繁体   English   中英

如何根据 Java 8 个流的值合并两个映射?

[英]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}

这是一种方法。

  • 首先遍历地图列表。
  • 对于每个 map,按要求处理密钥
    • 特殊键是itemNumberquantity
    • 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM