如何根据 Java 8 个流的值合并两个映射?
How to merge two Maps based on values with Java 8 streams?
我有 Collection
个 Map
包含库存信息:
0
"subtype" -> "DAIRY"
"itemNumber" -> "EU999"
"quantity" -> "60"
1
"subtype" -> "DAIRY"
"itemNumber" -> "EU999"
"quantity" -> "1000"
2
"subtype" -> "FRESH"
"itemNumber" -> "EU999"
"quantity" -> "800"
3
"subtype" -> "FRESH"
"itemNumber" -> "EU100"
"quantity" -> "100"
我需要根据 itemNumber
压缩此列表,同时对 quantity
求和并在逗号分隔的字符串中保留唯一的 subtypes
。意思是,new Map
s 看起来像这样:
0
"subtype" -> "DAIRY, FRESH"
"itemNumber" -> "EU999"
"quantity" -> "1860"
1
"subtype" -> "FRESH"
"itemNumber" -> "EU100"
"quantity" -> "100"
我尝试了各种流、收集器、groupby 等,但我迷路了。
这是我目前拥有的:
public Collection<Map> mergeInventoryPerItemNumber(Collection<Map> InventoryMap){
Map condensedInventory = null;
InventoryMap.stream()
.collect(groupingBy(inv -> new ImmutablePair<>(inv.get("itemNumber"), inv.get("subtype")))), collectingAndThen(toList(), list -> {
long count = list.stream()
.map(list.get(Integer.parseInt("quantity")))
.collect(counting());
String itemNumbers = list.stream()
.map(list.get("subtype"))
.collect(joining(" , "));
condensedInventory.put("quantity", count);
condensedInventory.put("subtype", itemNumbers);
return condensedInventory;
});
也许一次扫描就可以做到这一点,但在这里我通过两次解决了这个问题:一次将相似的项目组合在一起,另一次在每个组中的项目上构建一个代表性项目(这似乎在精神上与您的代码相似,您也试图从组中流式传输元素)。
public static Collection<Map<String, String>>
mergeInventoryPerItemNumber(Collection<Map<String, String>> m){
return m.stream()
// returns a map of itemNumber -> list of products with that number
.collect(Collectors.groupingBy(o -> o.get("itemNumber")))
// for each item number, builds new representative product
.entrySet().stream().map(e -> Map.of(
"itemNumber", e.getKey(),
// ... merging non-duplicate subtypes
"subtype", e.getValue().stream()
.map(v -> v.get("subtype"))
.distinct() // avoid duplicates
.collect(Collectors.joining(", ")),
// ... adding up quantities
"quantity", ""+e.getValue().stream()
.map(v -> Integer.parseInt(v.get("quantity")))
.reduce(0, Integer::sum)))
.collect(Collectors.toList());
}
public static void main(String ... args) {
Collection<Map<String, String>> c = mkMap();
dump(c);
dump(mergeInventoryPerItemNumber(c));
}
public static Collection<Map<String, String>> mkMap() {
return List.of(
Map.of("subtype", "DAIRY", "itemNumber", "EU999", "quantity", "60"),
Map.of("subtype", "DAIRY", "itemNumber", "EU999", "quantity", "1000"),
Map.of("subtype", "FRESH", "itemNumber", "EU999", "quantity", "800"),
Map.of("subtype", "FRESH", "itemNumber", "EU100", "quantity", "100"));
}
public static void dump(Collection<Map<String, String>> col) {
int i = 0;
for (Map<String, String> m : col) {
System.out.println(i++);
for (Map.Entry e : m.entrySet()) {
System.out.println("\t" + e.getKey() + " -> " + e.getValue());
}
}
}
您在这里误用了 Map
。每个地图都包含相同的键(“subtype”、“itemNumber”、“quantity”)。在您的代码中,它们几乎被视为对象属性。它们应该出现在每个地图中,并且每个地图都应该具有特定范围的值,尽管根据您的示例存储为字符串。
Side-note: 避免使用行类型(如 Map
没有尖括号 <>
中的通用信息),在这种情况下,集合中的所有元素都将被视为 Object
s.
Item
显然必须定义为 class。通过将这些数据存储在 map 中,您将失去为每个 [=112] 定义 适当数据类型 的可能性=],而且您无法 定义行为 来使用这些 属性(for更详细的解释请看 ).
public class Item {
private final String itemNumber;
private Set<Subtype> subtypes;
private long quantity;
public Item combine(Item other) {
Set<Subtype> combinedSubtypes = new HashSet<>(subtypes);
combinedSubtypes.addAll(other.subtypes);
return new Item(this.itemNumber,
combinedSubtypes,
this.quantity + other.quantity);
}
// + constructor, getters, hashCode/equals, toString
}
方法combine
表示将两个项目合并在一起的逻辑。通过将它放在这个 class 中,您可以轻松地重用它并在需要时更改它。
subtype
字段类型的最佳选择是 enum。因为它可以 避免错误 由拼写错误的字符串值引起,而且 enums 有广泛的语言支持(switch
表达式和语句,特别为枚举设计的特殊数据结构, enum 可以与 annotations) 一起使用。
这个自定义 enum 可以像这样。
public enum Subtype {DAIRY, FRESH}
通过所有这些更改,mergeInventoryPerItemNumber()
中的代码变得简洁易懂。 Collectors.groupingBy()
用于通过将 项 与相同的 itemNumber
分组来创建地图。下游收集器 Collectors.reducing()
用于将分组在同一 key 下的 items 组合为 单个对象 .
请注意 Collectors.reducing()
会产生 Optional
结果。因此,filter(Optional::isPresent)
用作预防措施,以确保结果存在,后续操作 map(Optional::get)
从 可选对象 item 中提取 item =81=].
public static Collection<Item> mergeInventoryPerItemNumber(Collection<Item> inventory) {
return inventory.stream()
.collect(Collectors.groupingBy(Item::getItemNumber,
Collectors.reducing(Item::combine)))
.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
main()
public static void main(String[] args) {
List<Item> inventory =
List.of(new Item("EU999", Set.of(Subtype.DAIRY), 60),
new Item("EU999", Set.of(Subtype.DAIRY), 1000),
new Item("EU999", Set.of(Subtype.FRESH), 800),
new Item("EU100", Set.of(Subtype.FRESH), 100));
Collection<Item> combinedItems = mergeInventoryPerItemNumber(inventory);
combinedItems.forEach(System.out::println);
}
输出
Item{itemNumber='EU100', subtypes=[FRESH], quantity=100}
Item{itemNumber='EU999', subtypes=[FRESH, DAIRY], quantity=1860}
这是一种方法。
- 首先遍历地图列表。
- 对于每个地图,根据需要处理键
- 特殊键是
itemNumber
和 quantity
itemNumber
是所有值的连接元素。
quantity
是必须作为整数对待的值
- 其他是字符串并被视为字符串(对于所有其他值,如果该值已存在于连接值的字符串中,则不会再次添加)
一些数据
List<Map<String, String>> mapList = List.of(
Map.of("subtype", "DAIRY", "itemNumber", "EU999",
"quantity", "60"),
Map.of("subtype", "DAIRY", "itemNumber", "EU999",
"quantity", "1000"),
Map.of("subtype", "FRESH", "itemNumber", "EU999",
"quantity", "800"),
Map.of("subtype", "FRESH", "itemNumber", "EU100",
"quantity", "100"));
构建过程
Map<String, Map<String, String>> result = new HashMap<>();
for (Map<String, String> m : mapList) {
result.compute(m.get("itemNumber"), (k, v) -> {
for (Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
String value = e.getValue();
if (v == null) {
v = new HashMap<String, String>();
v.put(key, value);
} else {
if (key.equals("quantity")) {
v.compute(key,
(kk, vv) -> vv == null ? value :
Integer.toString(Integer
.valueOf(vv)
+ Integer.valueOf(
value)));
} else {
v.compute(key, (kk, vv) -> vv == null ?
value : (vv.contains(value) ? vv :
vv + ", " + value));
}
}
}
return v;
});
}
List<Map<String,String>> list = new ArrayList<>(result.values());
for (int i = 0; i < list.size(); i++) {
System.out.println(i + " " + list.get(i));
}
打印
0 {itemNumber=EU100, quantity=100, subtype=FRESH}
1 {itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}
请注意,地图中的地图可能比地图列表更有用。例如,您可以通过简单地指定所需的键来检索 itemNumber
的映射。
System.out.println(result.get("EU999"));
打印
{itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}
我有 Collection
个 Map
包含库存信息:
0
"subtype" -> "DAIRY"
"itemNumber" -> "EU999"
"quantity" -> "60"
1
"subtype" -> "DAIRY"
"itemNumber" -> "EU999"
"quantity" -> "1000"
2
"subtype" -> "FRESH"
"itemNumber" -> "EU999"
"quantity" -> "800"
3
"subtype" -> "FRESH"
"itemNumber" -> "EU100"
"quantity" -> "100"
我需要根据 itemNumber
压缩此列表,同时对 quantity
求和并在逗号分隔的字符串中保留唯一的 subtypes
。意思是,new Map
s 看起来像这样:
0
"subtype" -> "DAIRY, FRESH"
"itemNumber" -> "EU999"
"quantity" -> "1860"
1
"subtype" -> "FRESH"
"itemNumber" -> "EU100"
"quantity" -> "100"
我尝试了各种流、收集器、groupby 等,但我迷路了。
这是我目前拥有的:
public Collection<Map> mergeInventoryPerItemNumber(Collection<Map> InventoryMap){
Map condensedInventory = null;
InventoryMap.stream()
.collect(groupingBy(inv -> new ImmutablePair<>(inv.get("itemNumber"), inv.get("subtype")))), collectingAndThen(toList(), list -> {
long count = list.stream()
.map(list.get(Integer.parseInt("quantity")))
.collect(counting());
String itemNumbers = list.stream()
.map(list.get("subtype"))
.collect(joining(" , "));
condensedInventory.put("quantity", count);
condensedInventory.put("subtype", itemNumbers);
return condensedInventory;
});
也许一次扫描就可以做到这一点,但在这里我通过两次解决了这个问题:一次将相似的项目组合在一起,另一次在每个组中的项目上构建一个代表性项目(这似乎在精神上与您的代码相似,您也试图从组中流式传输元素)。
public static Collection<Map<String, String>>
mergeInventoryPerItemNumber(Collection<Map<String, String>> m){
return m.stream()
// returns a map of itemNumber -> list of products with that number
.collect(Collectors.groupingBy(o -> o.get("itemNumber")))
// for each item number, builds new representative product
.entrySet().stream().map(e -> Map.of(
"itemNumber", e.getKey(),
// ... merging non-duplicate subtypes
"subtype", e.getValue().stream()
.map(v -> v.get("subtype"))
.distinct() // avoid duplicates
.collect(Collectors.joining(", ")),
// ... adding up quantities
"quantity", ""+e.getValue().stream()
.map(v -> Integer.parseInt(v.get("quantity")))
.reduce(0, Integer::sum)))
.collect(Collectors.toList());
}
public static void main(String ... args) {
Collection<Map<String, String>> c = mkMap();
dump(c);
dump(mergeInventoryPerItemNumber(c));
}
public static Collection<Map<String, String>> mkMap() {
return List.of(
Map.of("subtype", "DAIRY", "itemNumber", "EU999", "quantity", "60"),
Map.of("subtype", "DAIRY", "itemNumber", "EU999", "quantity", "1000"),
Map.of("subtype", "FRESH", "itemNumber", "EU999", "quantity", "800"),
Map.of("subtype", "FRESH", "itemNumber", "EU100", "quantity", "100"));
}
public static void dump(Collection<Map<String, String>> col) {
int i = 0;
for (Map<String, String> m : col) {
System.out.println(i++);
for (Map.Entry e : m.entrySet()) {
System.out.println("\t" + e.getKey() + " -> " + e.getValue());
}
}
}
您在这里误用了 Map
。每个地图都包含相同的键(“subtype”、“itemNumber”、“quantity”)。在您的代码中,它们几乎被视为对象属性。它们应该出现在每个地图中,并且每个地图都应该具有特定范围的值,尽管根据您的示例存储为字符串。
Side-note: 避免使用行类型(如 Map
没有尖括号 <>
中的通用信息),在这种情况下,集合中的所有元素都将被视为 Object
s.
Item
显然必须定义为 class。通过将这些数据存储在 map 中,您将失去为每个 [=112] 定义 适当数据类型 的可能性=],而且您无法 定义行为 来使用这些 属性(for更详细的解释请看
public class Item {
private final String itemNumber;
private Set<Subtype> subtypes;
private long quantity;
public Item combine(Item other) {
Set<Subtype> combinedSubtypes = new HashSet<>(subtypes);
combinedSubtypes.addAll(other.subtypes);
return new Item(this.itemNumber,
combinedSubtypes,
this.quantity + other.quantity);
}
// + constructor, getters, hashCode/equals, toString
}
方法combine
表示将两个项目合并在一起的逻辑。通过将它放在这个 class 中,您可以轻松地重用它并在需要时更改它。
subtype
字段类型的最佳选择是 enum。因为它可以 避免错误 由拼写错误的字符串值引起,而且 enums 有广泛的语言支持(switch
表达式和语句,特别为枚举设计的特殊数据结构, enum 可以与 annotations) 一起使用。
这个自定义 enum 可以像这样。
public enum Subtype {DAIRY, FRESH}
通过所有这些更改,mergeInventoryPerItemNumber()
中的代码变得简洁易懂。 Collectors.groupingBy()
用于通过将 项 与相同的 itemNumber
分组来创建地图。下游收集器 Collectors.reducing()
用于将分组在同一 key 下的 items 组合为 单个对象 .
请注意 Collectors.reducing()
会产生 Optional
结果。因此,filter(Optional::isPresent)
用作预防措施,以确保结果存在,后续操作 map(Optional::get)
从 可选对象 item 中提取 item =81=].
public static Collection<Item> mergeInventoryPerItemNumber(Collection<Item> inventory) {
return inventory.stream()
.collect(Collectors.groupingBy(Item::getItemNumber,
Collectors.reducing(Item::combine)))
.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
main()
public static void main(String[] args) {
List<Item> inventory =
List.of(new Item("EU999", Set.of(Subtype.DAIRY), 60),
new Item("EU999", Set.of(Subtype.DAIRY), 1000),
new Item("EU999", Set.of(Subtype.FRESH), 800),
new Item("EU100", Set.of(Subtype.FRESH), 100));
Collection<Item> combinedItems = mergeInventoryPerItemNumber(inventory);
combinedItems.forEach(System.out::println);
}
输出
Item{itemNumber='EU100', subtypes=[FRESH], quantity=100}
Item{itemNumber='EU999', subtypes=[FRESH, DAIRY], quantity=1860}
这是一种方法。
- 首先遍历地图列表。
- 对于每个地图,根据需要处理键
- 特殊键是
itemNumber
和quantity
itemNumber
是所有值的连接元素。quantity
是必须作为整数对待的值- 其他是字符串并被视为字符串(对于所有其他值,如果该值已存在于连接值的字符串中,则不会再次添加)
- 特殊键是
一些数据
List<Map<String, String>> mapList = List.of(
Map.of("subtype", "DAIRY", "itemNumber", "EU999",
"quantity", "60"),
Map.of("subtype", "DAIRY", "itemNumber", "EU999",
"quantity", "1000"),
Map.of("subtype", "FRESH", "itemNumber", "EU999",
"quantity", "800"),
Map.of("subtype", "FRESH", "itemNumber", "EU100",
"quantity", "100"));
构建过程
Map<String, Map<String, String>> result = new HashMap<>();
for (Map<String, String> m : mapList) {
result.compute(m.get("itemNumber"), (k, v) -> {
for (Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
String value = e.getValue();
if (v == null) {
v = new HashMap<String, String>();
v.put(key, value);
} else {
if (key.equals("quantity")) {
v.compute(key,
(kk, vv) -> vv == null ? value :
Integer.toString(Integer
.valueOf(vv)
+ Integer.valueOf(
value)));
} else {
v.compute(key, (kk, vv) -> vv == null ?
value : (vv.contains(value) ? vv :
vv + ", " + value));
}
}
}
return v;
});
}
List<Map<String,String>> list = new ArrayList<>(result.values());
for (int i = 0; i < list.size(); i++) {
System.out.println(i + " " + list.get(i));
}
打印
0 {itemNumber=EU100, quantity=100, subtype=FRESH}
1 {itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}
请注意,地图中的地图可能比地图列表更有用。例如,您可以通过简单地指定所需的键来检索 itemNumber
的映射。
System.out.println(result.get("EU999"));
打印
{itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}