Java 流 - 分组依据和 return 嵌套映射
Java Streams - group-by and return a Nested Map
我的数据是这样的,
unitId time value1 value2
a 2021 10 11
a 2022 15 13
b 2021 20 25
b 2022 30 37
我的目标是将每个 unitId 和值放入这样的地图中,
{
'a': {'2021_value1': 10, '2021_value2': 11, '2022_value1': 15, '2022_value2': 13},
'b': {'2021_value1': 20, '2021_value2': 25, '2022_value1': 30, '2022_value2': 37},
}
我已经找到了两种实现方法,这是我的代码,
public class Unit {
public String unitId;
public Integer year;
public Integer value1;
public Integer value2;
public static Unit of(String unitId, Integer year, Integer value1, Integer value2) {
Unit unit = new Unit();
unit.unitId = unitId;
unit.year = year;
unit.value1 = value1;
unit.value2 = value2;
return unit;
}
}
并且
public class UnitTest {
private static void printMap(Map<String, Map<String, Integer>> map) {
map.forEach((k, v) -> {
String vStr = v.entrySet().stream().map(a -> String.format("%s: %s", a.getKey(), a.getValue())).collect(Collectors.joining(", "));
System.out.printf("%s: {%s}%n", k, vStr);
});
}
public static void main(String[] args) {
List<Unit> list = new ArrayList<>();
list.add(Unit.of("a", 2021, 10, 11 ));
list.add(Unit.of("a", 2022, 15, 13));
list.add(Unit.of("b", 2021, 20, 25));
list.add(Unit.of("b", 2022, 30, 37));
Map<String, Map<String, Integer>> map1 = list.stream().collect(
Collectors.groupingBy(
x -> x.unitId,
Collector.of(
HashMap::new,
(x, y) -> {
x.put(String.format("%s_%s", y.year, "value1"), y.value1);
x.put(String.format("%s_%s", y.year, "value2"), y.value2);
},
(x, y) -> {x.putAll(y); return x;}
)
)
);
Map<String, Map<String, Integer>> map2 = list.stream().collect(
Collectors.groupingBy(
x -> x.unitId,
Collectors.collectingAndThen(
Collectors.toList(),
x -> x.stream()
.flatMap(y -> Stream.of(
new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value1"), y.value1),
new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value2"), y.value2)
))
.collect(Collectors.toMap(
AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue)))
)
);
printMap(map1);
printMap(map2);
}
}
第一个更像是手动编写处理,第二个使用可能没有必要的临时列表。有什么直接或简单的方法可以做到这一点,比如使用 Collectors.toMap API 或其他方法?
Is there any direct or simple way to do this, like use Collectors.toMap API or something else?
如果您只想使用 built-in 个收集器,您可以尝试组合使用 groupingBy()
和 teeing()
。
Collectors.teeing()
需要三个 参数 :2
下游 收集器 和一个 merger
函数。流中的每个元素都将传递到两个 收集器 中,当这些收集器完成后,它们产生的结果将由 函数 合并。
在下面的代码中,toMap()
用作 teeing()
的 下游收集器 。这些 收集器 中的每一个都负责检索其类型的 value.
代码可能如下所示:
public static void main(String[] args) {
List<Unit> list =
List.of(Unit.of("a", 2021, 10, 11 ),
Unit.of("a", 2022, 15, 13),
Unit.of("b", 2021, 20, 25),
Unit.of("b", 2022, 30, 37));
Map<String, Map<String, Integer>> map = list.stream()
.collect(Collectors.groupingBy(Unit::getUnitId,
Collectors.teeing(
Collectors.toMap(
unit -> unit.getYear() + "_value1",
Unit::getValue1),
Collectors.toMap(
unit -> unit.getYear() + "_value2",
Unit::getValue2),
(values1, values2) -> {values1.putAll(values2); return values1;})
));
printMap(map);
}
输出:
a: {2022_value2: 13, 2021_value1: 10, 2022_value1: 15, 2021_value2: 11}
b: {2022_value2: 37, 2021_value1: 20, 2022_value1: 30, 2021_value2: 25}
注:
- 如果考虑性能,
Collector.of()
会稍微好一些,因为它不会创建中间集合。
- 为了使这种方法正常工作(我指的是上面列出的代码以及问题中的代码),
unitId
和 year
的每个组合都应该是唯一的。否则,请考虑添加解决重复项的逻辑。
我的数据是这样的,
unitId time value1 value2
a 2021 10 11
a 2022 15 13
b 2021 20 25
b 2022 30 37
我的目标是将每个 unitId 和值放入这样的地图中,
{
'a': {'2021_value1': 10, '2021_value2': 11, '2022_value1': 15, '2022_value2': 13},
'b': {'2021_value1': 20, '2021_value2': 25, '2022_value1': 30, '2022_value2': 37},
}
我已经找到了两种实现方法,这是我的代码,
public class Unit {
public String unitId;
public Integer year;
public Integer value1;
public Integer value2;
public static Unit of(String unitId, Integer year, Integer value1, Integer value2) {
Unit unit = new Unit();
unit.unitId = unitId;
unit.year = year;
unit.value1 = value1;
unit.value2 = value2;
return unit;
}
}
并且
public class UnitTest {
private static void printMap(Map<String, Map<String, Integer>> map) {
map.forEach((k, v) -> {
String vStr = v.entrySet().stream().map(a -> String.format("%s: %s", a.getKey(), a.getValue())).collect(Collectors.joining(", "));
System.out.printf("%s: {%s}%n", k, vStr);
});
}
public static void main(String[] args) {
List<Unit> list = new ArrayList<>();
list.add(Unit.of("a", 2021, 10, 11 ));
list.add(Unit.of("a", 2022, 15, 13));
list.add(Unit.of("b", 2021, 20, 25));
list.add(Unit.of("b", 2022, 30, 37));
Map<String, Map<String, Integer>> map1 = list.stream().collect(
Collectors.groupingBy(
x -> x.unitId,
Collector.of(
HashMap::new,
(x, y) -> {
x.put(String.format("%s_%s", y.year, "value1"), y.value1);
x.put(String.format("%s_%s", y.year, "value2"), y.value2);
},
(x, y) -> {x.putAll(y); return x;}
)
)
);
Map<String, Map<String, Integer>> map2 = list.stream().collect(
Collectors.groupingBy(
x -> x.unitId,
Collectors.collectingAndThen(
Collectors.toList(),
x -> x.stream()
.flatMap(y -> Stream.of(
new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value1"), y.value1),
new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value2"), y.value2)
))
.collect(Collectors.toMap(
AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue)))
)
);
printMap(map1);
printMap(map2);
}
}
第一个更像是手动编写处理,第二个使用可能没有必要的临时列表。有什么直接或简单的方法可以做到这一点,比如使用 Collectors.toMap API 或其他方法?
Is there any direct or simple way to do this, like use Collectors.toMap API or something else?
如果您只想使用 built-in 个收集器,您可以尝试组合使用 groupingBy()
和 teeing()
。
Collectors.teeing()
需要三个 参数 :2
下游 收集器 和一个 merger
函数。流中的每个元素都将传递到两个 收集器 中,当这些收集器完成后,它们产生的结果将由 函数 合并。
在下面的代码中,toMap()
用作 teeing()
的 下游收集器 。这些 收集器 中的每一个都负责检索其类型的 value.
代码可能如下所示:
public static void main(String[] args) {
List<Unit> list =
List.of(Unit.of("a", 2021, 10, 11 ),
Unit.of("a", 2022, 15, 13),
Unit.of("b", 2021, 20, 25),
Unit.of("b", 2022, 30, 37));
Map<String, Map<String, Integer>> map = list.stream()
.collect(Collectors.groupingBy(Unit::getUnitId,
Collectors.teeing(
Collectors.toMap(
unit -> unit.getYear() + "_value1",
Unit::getValue1),
Collectors.toMap(
unit -> unit.getYear() + "_value2",
Unit::getValue2),
(values1, values2) -> {values1.putAll(values2); return values1;})
));
printMap(map);
}
输出:
a: {2022_value2: 13, 2021_value1: 10, 2022_value1: 15, 2021_value2: 11}
b: {2022_value2: 37, 2021_value1: 20, 2022_value1: 30, 2021_value2: 25}
注:
- 如果考虑性能,
Collector.of()
会稍微好一些,因为它不会创建中间集合。 - 为了使这种方法正常工作(我指的是上面列出的代码以及问题中的代码),
unitId
和year
的每个组合都应该是唯一的。否则,请考虑添加解决重复项的逻辑。