[英]How to merge two Maps based on values with Java 8 streams?
我有一个包含库存信息的Map
Collection
:
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
。 意思是,新的Map
看起来像这样:
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;
});
您在这里滥用了Map
。 每个 map 都包含相同的键( “subtype”、“itemNumber”、“quantity” )。 在您的代码中,它们几乎被视为 object 属性。 它们应该出现在每个 map 中,并且它们每个都应该具有特定的值范围,尽管根据您的示例存储为字符串。
旁注:避免使用行类型(如Map
,但尖括号<>
中没有通用信息),在这种情况下,集合中的所有元素都将被视为Object
s。
Item
显然必须定义为class 。 通过将这些数据存储在map中,您将失去为每个属性定义适当数据类型的可能性,而且您也无法定义使用这些属性进行操作的行为(有关更详细的解释,请查看此答案).
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 。 因为它可以避免由拼写错误的字符串值引起的错误,而且枚举具有广泛的语言支持( switch
表达式和语句,专为枚举设计的特殊数据结构,枚举可以与注释一起使用)。
这个自定义枚举看起来像这样。
public enum Subtype {DAIRY, FRESH}
通过所有这些更改, mergeInventoryPerItemNumber()
中的代码变得简洁易懂。 Collectors.groupingBy()
用于通过对具有相同itemNumber
的项目进行分组来创建 map 。 下游收集器Collectors.reducing()
用于将在同一键下分组的项目合并为单个 object 。
请注意, Collectors.reducing()
产生一个Optional
结果。 因此, filter(Optional::isPresent)
用作预防措施以确保结果存在,并且后续操作map(Optional::get)
从可选的 object中提取项目。
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());
}
主要的()
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);
}
Output
Item{itemNumber='EU100', subtypes=[FRESH], quantity=100}
Item{itemNumber='EU999', subtypes=[FRESH, DAIRY], quantity=1860}
这是一种方法。
itemNumber
和quantity
itemNumber
是所有值的连接元素。quantity
是必须被视为 integer 的值一些数据
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}
请注意,地图的 map 可能比地图列表更有用。 例如,您可以通过简单地指定所需的键来检索 itemNumber 的itemNumber
。
System.out.println(result.get("EU999"));
印刷
{itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}
一次扫描就可以做到这一点,但在这里我用两次通过解决了这个问题:一次将相似的项目组合在一起,另一次在每个组中的项目上构建一个代表性项目(这看起来与你的精神相似代码,您还尝试从组中获取 stream 个元素)。
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());
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.