Skip to content

Commit 6057edf

Browse files
authored
Merge pull request #2 from lokic/feature/join
feat: 增加join相关功能
2 parents 8acd0f2 + 3af261b commit 6057edf

File tree

5 files changed

+211
-2
lines changed

5 files changed

+211
-2
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.github.lokic</groupId>
88
<artifactId>java-plus</artifactId>
9-
<version>0.0.13</version>
9+
<version>0.0.14</version>
1010
<name>java-plus</name>
1111
<description>Provide some useful extensions on the basis of java8 to make java more usable.</description>
1212
<url>https://github.com/lokic/java-plus</url>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.github.lokic.javaplus;
2+
3+
import com.github.lokic.javaplus.tuple.Tuple;
4+
import com.github.lokic.javaplus.tuple.Tuple2;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
import java.util.function.Function;
11+
import java.util.function.Predicate;
12+
import java.util.stream.Collector;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
15+
16+
public class Join {
17+
18+
19+
public static <T1, T2> JoinType<T1, T2> innerJoin(Stream<T1> left, Stream<T2> right) {
20+
return new JoinType<>(left, right, t -> t.getT1() != null && t.getT2() != null);
21+
}
22+
23+
public static <T1, T2> JoinType<T1, T2> leftOuterJoin(Stream<T1> left, Stream<T2> right) {
24+
return new JoinType<>(left, right, t -> t.getT1() != null);
25+
}
26+
27+
public static <T1, T2> JoinType<T1, T2> rightOuterJoin(Stream<T1> left, Stream<T2> right) {
28+
return new JoinType<>(left, right, t -> t.getT2() != null);
29+
}
30+
31+
public static <T1, T2> JoinType<T1, T2> fullOuterJoin(Stream<T1> left, Stream<T2> right) {
32+
return new JoinType<>(left, right, t -> !(t.getT1() == null && t.getT2() == null));
33+
}
34+
35+
36+
public static class JoinType<T1, T2> {
37+
private final Stream<Tuple2<T1, T2>> leftWrappedStream;
38+
private final Stream<Tuple2<T1, T2>> rightWrappedStream;
39+
private final Predicate<Tuple2<T1, T2>> joinMatcher;
40+
41+
public JoinType(Stream<T1> left, Stream<T2> right, Predicate<Tuple2<T1, T2>> joinMatcher) {
42+
this.leftWrappedStream = left.map(l -> Tuple.of(Objects.requireNonNull(l), null));
43+
this.rightWrappedStream = right.map(r -> Tuple.of(null, Objects.requireNonNull(r)));
44+
this.joinMatcher = joinMatcher;
45+
}
46+
47+
public <K> Stream<Tuple2<T1, T2>> on(Function<T1, K> leftKey, Function<T2, K> rightKey) {
48+
return Stream.concat(leftWrappedStream, rightWrappedStream)
49+
.collect(Collectors.groupingBy(t -> matchKey(t, leftKey, rightKey)))
50+
.values()
51+
.stream()
52+
.flatMap(this::product)
53+
.filter(this.joinMatcher);
54+
}
55+
56+
private Stream<Tuple2<T1, T2>> product(List<Tuple2<T1, T2>> li) {
57+
Map<Boolean, List<Tuple2<T1, T2>>> map = li.stream()
58+
.collect(Collectors.partitioningBy(this::isLeft, this.toListOrNullList()));
59+
List<Tuple2<T1, T2>> left = map.get(true);
60+
List<Tuple2<T1, T2>> right = map.get(false);
61+
return left.stream()
62+
.flatMap(l -> right.stream()
63+
.map(r -> this.merge(l, r)));
64+
}
65+
66+
private Tuple2<T1, T2> merge(Tuple2<T1, T2> l, Tuple2<T1, T2> r) {
67+
return Tuple.of(l == null ? null : l.getT1(), r == null ? null : r.getT2());
68+
}
69+
70+
private boolean isLeft(Tuple2<T1, T2> t) {
71+
return t.getT1() != null;
72+
}
73+
74+
private <K> K matchKey(Tuple2<T1, T2> t, Function<T1, K> leftKey, Function<T2, K> rightKey) {
75+
if (t.getT1() != null) {
76+
return leftKey.apply(t.getT1());
77+
}
78+
if (t.getT2() != null) {
79+
return rightKey.apply(t.getT2());
80+
}
81+
throw new IllegalStateException("t1 == null and t2 == null");
82+
}
83+
84+
private <T> Collector<T, List<T>, List<T>> toListOrNullList() {
85+
return Collector.of(
86+
ArrayList::new,
87+
List::add,
88+
(left, right) -> {
89+
left.addAll(right);
90+
return left;
91+
},
92+
l -> {
93+
if (l == null || l.isEmpty()) {
94+
return nullList();
95+
}
96+
return l;
97+
}
98+
);
99+
}
100+
101+
private <T> List<T> nullList() {
102+
List<T> li = new ArrayList<>();
103+
li.add(null);
104+
return li;
105+
}
106+
}
107+
108+
}

src/main/java/com/github/lokic/javaplus/functional/entry/EntryFunctional.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
import com.github.lokic.javaplus.functional.consumer.Consumer2;
44
import com.github.lokic.javaplus.functional.function.Function2;
5+
import com.github.lokic.javaplus.functional.function.Function3;
56
import com.github.lokic.javaplus.functional.predicate.Predicate2;
7+
import com.github.lokic.javaplus.tuple.Tuple2;
68

79
public interface EntryFunctional {
810

911
static <K, V, R> EntryFunction<K, V, R> function(Function2<K, V, R> function2) {
1012
return function2::apply;
1113
}
1214

15+
static <T1, T2, V, R> EntryFunction<Tuple2<T1, T2>, V, R> function(Function3<T1, T2, V, R> function3) {
16+
return (t, v) -> function3.apply(t.getT1(), t.getT2(), v);
17+
}
18+
1319
static <K, V> EntryConsumer<K, V> consumer(Consumer2<K, V> consumer2) {
1420
return consumer2::accept;
1521
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.github.lokic.javaplus;
2+
3+
import com.github.lokic.javaplus.tuple.Tuple;
4+
import com.github.lokic.javaplus.tuple.Tuple2;
5+
import org.assertj.core.api.Assertions;
6+
import org.junit.Test;
7+
8+
import java.util.List;
9+
import java.util.function.Function;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.Stream;
12+
13+
public class JoinTest {
14+
15+
@Test
16+
public void test_innerJoin() {
17+
List<Tuple2<Integer, String>> list = Join.innerJoin(Stream.of(1, 2, 3, 4), Stream.of("2", "3", "5"))
18+
.on(String::valueOf, Function.identity())
19+
.collect(Collectors.toList());
20+
21+
Assertions.assertThat(list)
22+
.containsExactly(Tuple.of(2, "2"), Tuple.of(3, "3"));
23+
}
24+
25+
@Test
26+
public void test_innerJoin_repeatKey() {
27+
List<Tuple2<Integer, String>> list = Join.innerJoin(Stream.of(1, 2, 2, 3, 4), Stream.of("2", "3", "5"))
28+
.on(String::valueOf, Function.identity())
29+
.collect(Collectors.toList());
30+
31+
Assertions.assertThat(list)
32+
.containsExactly(Tuple.of(2, "2"), Tuple.of(2, "2"), Tuple.of(3, "3"));
33+
}
34+
35+
@Test
36+
public void test_leftOuterJoin() {
37+
List<Tuple2<Integer, String>> list = Join.leftOuterJoin(Stream.of(1, 2, 3, 4), Stream.of("2", "3", "5"))
38+
.on(String::valueOf, Function.identity())
39+
.collect(Collectors.toList());
40+
41+
Assertions.assertThat(list)
42+
.containsExactly(Tuple.of(1, null), Tuple.of(2, "2"), Tuple.of(3, "3"), Tuple.of(4, null));
43+
}
44+
45+
@Test
46+
public void test_leftOuterJoin_empty() {
47+
List<Tuple2<Integer, String>> list = Join.leftOuterJoin(Stream.of(1, 2, 3, 4), Stream.<String>empty())
48+
.on(String::valueOf, Function.identity())
49+
.collect(Collectors.toList());
50+
51+
Assertions.assertThat(list)
52+
.containsExactly(Tuple.of(1, null), Tuple.of(2, null), Tuple.of(3, null), Tuple.of(4, null));
53+
}
54+
55+
@Test
56+
public void test_rightOuterJoin() {
57+
List<Tuple2<Integer, String>> list = Join.rightOuterJoin(Stream.of(1, 2, 3, 4), Stream.of("2", "3", "5"))
58+
.on(String::valueOf, Function.identity())
59+
.collect(Collectors.toList());
60+
61+
Assertions.assertThat(list)
62+
.containsExactly(Tuple.of(2, "2"), Tuple.of(3, "3"), Tuple.of(null, "5"));
63+
}
64+
65+
66+
@Test
67+
public void test_fullOuterJoin() {
68+
List<Tuple2<Integer, String>> list = Join.fullOuterJoin(Stream.of(1, 2, 3, 4), Stream.of("2", "3", "5"))
69+
.on(String::valueOf, Function.identity())
70+
.collect(Collectors.toList());
71+
72+
Assertions.assertThat(list)
73+
.containsExactly(Tuple.of(1, null), Tuple.of(2, "2"), Tuple.of(3, "3"), Tuple.of(4, null), Tuple.of(null, "5"));
74+
}
75+
76+
}

src/test/java/com/github/lokic/javaplus/functional/entry/EntryFunctionalTest.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package com.github.lokic.javaplus.functional.entry;
22

3+
import com.github.lokic.javaplus.tuple.Tuple;
4+
import com.github.lokic.javaplus.tuple.Tuple2;
5+
import org.assertj.core.api.Assertions;
36
import org.junit.Test;
47

58
import java.util.HashMap;
9+
import java.util.LinkedHashMap;
10+
import java.util.List;
611
import java.util.Map;
712
import java.util.stream.Collectors;
813
import java.util.stream.Stream;
@@ -15,7 +20,7 @@ public void test_best_practice_ambiguous_method_call() {
1520
ageMaps.put(Stream.of("A"), Stream.of(19));
1621
ageMaps.put(Stream.of("B"), Stream.of(20));
1722
ageMaps.put(Stream.of("C"), Stream.of(20));
18-
23+
1924
// EntryFunction
2025
ageMaps.entrySet().stream()
2126
.map(EntryFunctional.function(this::zipEntryTest))
@@ -26,6 +31,20 @@ public void test_best_practice_ambiguous_method_call() {
2631
.forEach(EntryFunctional.consumer(this::zipEntryTest));
2732
}
2833

34+
@Test
35+
public void test_function3() {
36+
Map<Tuple2<Integer, String>, Long> map = new LinkedHashMap<>();
37+
map.put(Tuple.of(1, "A"), 11L);
38+
map.put(Tuple.of(2, "B"), 22L);
39+
map.put(Tuple.of(3, "C"), 33L);
40+
41+
List<String> re = map.entrySet().stream()
42+
.map(EntryFunctional.function((a, b, c) -> a + " " + b + " " + c))
43+
.collect(Collectors.toList());
44+
Assertions.assertThat(re)
45+
.containsExactly("1 A 11", "2 B 22", "3 C 33");
46+
}
47+
2948
public <A, B> Stream<Map.Entry<A, B>> zipEntryTest(Stream<A> streamA, Stream<B> streamB) {
3049
return Stream.empty();
3150
}

0 commit comments

Comments
 (0)