如何使用 Stream API 展平此 List<C1>,其中 C1 包含单个元素的 C2 集合?
How to flatten this List<C1> where C1 contains a collection of C2 of a single element using Stream API?
我有这样的结构(简化):
class C1 {
Integer id;
Set<C2> c2Set;
public C1 add(C2 c2) {
c2Set.add(c2);
return this;
}
//equals and hashcode methods based on id field.
@Override public String toString() {
return "C1: {" + id + ", " + c2Set + "}";
}
}
class C2 {
Integer id;
//equals and hashcode methods based on id field.
@Override
public String toString() {
return "C2: " + id;
}
}
为了填充这个结构,我执行了以下查询:
SELECT c1.id, c2.id
FROM tablex c1
INNER JOIN tablex_tabley d ON d.c1id = c1.id
INNER JOIN tabley c2 ON c2.id = d.c2id
ORDER BY c1.id;
这是直接从ResultSet
(使用JDBI)逐行读取的。读取的数据结构类似于:
List<C1> c1List = asList(
new C1(1).add(new C2(1)),
new C1(1).add(new C2(2)),
new C1(2).add(new C2(1))
);
我需要将此列表展平,使其看起来像这样创建的:
List<C1> c1List = asList(
new C1(1).add(new C2(1)).add(new C2(2)),
new C1(2).add(new C2(1))
);
目前我使用这段代码来完成这项工作:
Map<C1, C1> c1Map = new LinkedHashMap<>();
for (C1 c1 : c1List) {
if (!c1Map.containsKey(c1)) {
c1Map.put(c1, c1);
} else {
C1 prevC1 = c1Map.get(c1);
for (C2 c2 : c1.c2Set) {
prevC1.add(c2);
}
}
}
List<C1> c1ListReduce = new ArrayList<>(c1Map.values());
//just to check the results
System.out.println(c1ListReduce);
如何使用 Java 8 个流实现同样的效果?我找不到一种方法来将属于 C1 实例和 return 一个 List<C1>
(或任何其他集合,可能是一个 Set<C1>
)的所有 C2 实例分组,在不知道我已经访问过的元素并检索它们以添加它们。
看起来您所要做的就是按共同点 id
对元素进行分组,然后将它们合并为一个 C1
:
List<C1> newC1List = c1List.stream()
.collect(Collectors.groupingBy(C1::getId/*getters should be implementer*/))
.entrySet()
.stream()
.map(entry -> /*constructor should be implemented*/
new C1(entry.getKey(), entry.getValue().stream().flatMap(c1 -> c1.getC2Set().stream()).collect(Collectors.toSet())))
.collect(toList());
让我们逐步解释这段代码:
第一个 collect
行正在创建新的 Map
,它将 id
映射到具有此 id
的元素列表。
在接下来的两行中,我们将创建 Stream
对(实际上是 Map.Entry
),其中键是 id
,值是C1
的列表,其中有这个 id
.
下一个操作是将值中的所有 C1
合并为一个 C1
(Map.Entry<Integer, List<C1>> -> C1
)。 id
取自entry的key,虽然我可以用List
的第一个元素的id,但是看起来比较丑:)
所以,元素的id
就是entry.getKey()
,而c2Set
是将C1
里面的所有Set
合并为一个Set
。使用 Stream
的 flatMap
操作可以轻松完成此操作(Haskell 的 concatMap
模拟)。
我用 collect
方法将 Stream
转换为 List
。
本着 Dmitry Ginzburg 的相同精神,但实施方式不同。
List<C1> list = c1List.stream()
.collect(toMap(c -> c.id, c -> c.c2Set, (set1, set2) -> Stream.concat(set1.stream(), set2.stream()).collect(toSet())))
.entrySet()
.stream()
.map(e -> new C1(e.getKey(), e.getValue()))
.collect(toList());
它使用 toMap()
收集器。从 List<C1>
,您创建一个 Map<Integer, Set<C2>>
,将每个 C1 的 id 映射到它的集合 c2Set
。如果您有一个相似的 ID,则将这两个值(即集合)合并到另一组新集合中。
最后您获得条目集并将每个条目映射到一个新的 C1 实例,然后将所有实例收集到一个列表中。
(我假设有一个接受 Set 作为参数的构造函数)
我有这样的结构(简化):
class C1 {
Integer id;
Set<C2> c2Set;
public C1 add(C2 c2) {
c2Set.add(c2);
return this;
}
//equals and hashcode methods based on id field.
@Override public String toString() {
return "C1: {" + id + ", " + c2Set + "}";
}
}
class C2 {
Integer id;
//equals and hashcode methods based on id field.
@Override
public String toString() {
return "C2: " + id;
}
}
为了填充这个结构,我执行了以下查询:
SELECT c1.id, c2.id
FROM tablex c1
INNER JOIN tablex_tabley d ON d.c1id = c1.id
INNER JOIN tabley c2 ON c2.id = d.c2id
ORDER BY c1.id;
这是直接从ResultSet
(使用JDBI)逐行读取的。读取的数据结构类似于:
List<C1> c1List = asList(
new C1(1).add(new C2(1)),
new C1(1).add(new C2(2)),
new C1(2).add(new C2(1))
);
我需要将此列表展平,使其看起来像这样创建的:
List<C1> c1List = asList(
new C1(1).add(new C2(1)).add(new C2(2)),
new C1(2).add(new C2(1))
);
目前我使用这段代码来完成这项工作:
Map<C1, C1> c1Map = new LinkedHashMap<>();
for (C1 c1 : c1List) {
if (!c1Map.containsKey(c1)) {
c1Map.put(c1, c1);
} else {
C1 prevC1 = c1Map.get(c1);
for (C2 c2 : c1.c2Set) {
prevC1.add(c2);
}
}
}
List<C1> c1ListReduce = new ArrayList<>(c1Map.values());
//just to check the results
System.out.println(c1ListReduce);
如何使用 Java 8 个流实现同样的效果?我找不到一种方法来将属于 C1 实例和 return 一个 List<C1>
(或任何其他集合,可能是一个 Set<C1>
)的所有 C2 实例分组,在不知道我已经访问过的元素并检索它们以添加它们。
看起来您所要做的就是按共同点 id
对元素进行分组,然后将它们合并为一个 C1
:
List<C1> newC1List = c1List.stream()
.collect(Collectors.groupingBy(C1::getId/*getters should be implementer*/))
.entrySet()
.stream()
.map(entry -> /*constructor should be implemented*/
new C1(entry.getKey(), entry.getValue().stream().flatMap(c1 -> c1.getC2Set().stream()).collect(Collectors.toSet())))
.collect(toList());
让我们逐步解释这段代码:
第一个
collect
行正在创建新的Map
,它将id
映射到具有此id
的元素列表。在接下来的两行中,我们将创建
Stream
对(实际上是Map.Entry
),其中键是id
,值是C1
的列表,其中有这个id
.下一个操作是将值中的所有
C1
合并为一个C1
(Map.Entry<Integer, List<C1>> -> C1
)。id
取自entry的key,虽然我可以用List
的第一个元素的id,但是看起来比较丑:)所以,元素的
id
就是entry.getKey()
,而c2Set
是将C1
里面的所有Set
合并为一个Set
。使用Stream
的flatMap
操作可以轻松完成此操作(Haskell 的concatMap
模拟)。我用
collect
方法将Stream
转换为List
。
本着 Dmitry Ginzburg 的相同精神,但实施方式不同。
List<C1> list = c1List.stream()
.collect(toMap(c -> c.id, c -> c.c2Set, (set1, set2) -> Stream.concat(set1.stream(), set2.stream()).collect(toSet())))
.entrySet()
.stream()
.map(e -> new C1(e.getKey(), e.getValue()))
.collect(toList());
它使用 toMap()
收集器。从 List<C1>
,您创建一个 Map<Integer, Set<C2>>
,将每个 C1 的 id 映射到它的集合 c2Set
。如果您有一个相似的 ID,则将这两个值(即集合)合并到另一组新集合中。
最后您获得条目集并将每个条目映射到一个新的 C1 实例,然后将所有实例收集到一个列表中。
(我假设有一个接受 Set 作为参数的构造函数)