如何使用 Java 8 流根据值合并两个 Map?

发布于 2025-01-13 02:00:23 字数 1635 浏览 3 评论 0原文

我有一个包含库存信息的 MapCollection

 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 求和 并在逗号分隔的字符串中保留唯一的子类型。意思是, 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;
        });

I have a Collection of Maps containing inventory information:

 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"

I need to condense this list based on the itemNumber, while summing the quantity and retaining unique subtypes in a comma separated string. Meaning, new Maps would look like this:

 0 
  "subtype" -> "DAIRY, FRESH"
  "itemNumber" -> "EU999"
  "quantity" -> "1860"
 1 
  "subtype" -> "FRESH"
  "itemNumber" -> "EU100"
  "quantity" -> "100"

I've tried a variations of streams, collectors, groupby etc., and I'm lost.

This is what I have so far:

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;
        });

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

为人所爱 2025-01-20 02:00:23

这是一种方法。

  • 首先遍历地图列表。
  • 对于每个地图,根据需要处理键
    • 特殊键是itemNumberquantity
    • 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}

Here is one approach.

  • first iterate thru the list of maps.
  • for each map, process the keys as required
    • special keys are itemNumber and quantity
    • itemNumber is the joining element for all the values.
    • quantity is the value that must be treated as an integer
    • the others are strings and are treated as such (for all other values, if the value already exists in the string of concatenated values, then it is not added again)

Some data

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"));

The building process

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));
}

prints

0 {itemNumber=EU100, quantity=100, subtype=FRESH}
1 {itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}

Note that the map of maps may be more useful that a list of maps. For example, you can retrieve the map for the itemNumber by simply specifying the desired key.

System.out.println(result.get("EU999"));

prints

{itemNumber=EU999, quantity=1860, subtype=DAIRY, FRESH}

烈酒灼喉 2025-01-20 02:00:23

您在这里滥用了Map。每个地图都包含相同的键(“subtype”、“itemNumber”、“quantity”)。它们在代码中几乎被视为对象属性。它们应该出现在每个地图中,并且每个地图都应该具有特定的值范围,尽管根据您的示例存储为字符串。

旁注:避免使用行类型(例如Map,尖括号<>中没有通用信息),在这样的情况下在这种情况下,集合中的所有元素都将被视为Object

Item 显然必须定义为。通过将这些数据存储在地图中,您将失去为每个属性定义适当的数据类型的可能性,而且您无法定义行为来操纵这些属性有关更详细的解释,请查看这个答案)。

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 表示将两个项目合并在一起的逻辑。通过将其放置在这个中,您可以在需要时轻松重用和更改它。

subtype 字段类型的最佳选择是 枚举。因为它可以避免由拼写错误的字符串值引起的错误,并且枚举具有广泛的语言支持(switch 表达式< /em>和语句,专门为枚举设计的特殊数据结构enum可以与一起使用注释)。

这个自定义枚举看起来像这样。

public enum Subtype {DAIRY, FRESH}

经过所有这些更改,mergeInventoryPerItemNumber() 内的代码变得简洁且更易于理解。 Collectors.groupingBy() 用于通过将具有相同itemNumber项目分组来创建地图。下游收集器 Collectors.reducing() 用于将同一 key 下分组的合并为一个单个对象

请注意,Collectors.reducing() 会生成一个Optional 结果。因此,使用 filter(Optional::isPresent) 作为预防措施,以确保结果存在,并且后续操作 map(Optional::get) 提取 来自可选对象的项目

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());
}

ma​​in()

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}

You are misusing a Map here. Every map contains the same keys ("subtype", "itemNumber", "quantity"). And they are treated almost like object properties in your code. They are expected to be present in every map and each of them expected to have a specific range of values, although are stored as strings according to your example.

Side-note: avoid using row types (like Map without generic information in angle brackets <>), in such a case all elements inside a collection will be treated as Objects.

Item clearly has to be defined as a class. By storing these data inside a map, you're loosing a possibility to define an appropriate data type for each property, and also you're not able to define behaviour to manipulate with these properties (for more elaborate explanation take a look at this answer).

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
}

Method combine represents the logic for merging two items together. By placing it inside this class, you could easily reuse and change it when needed.

The best choice for the type of the subtype field is an enum. Because it'll allow to avoid mistakes caused by misspelled string values and also enums have an extensive language support (switch expressions and statements, special data structures designed especially for enums, enum could be used with annotations).

This custom enum can look like this.

public enum Subtype {DAIRY, FRESH}

With all these changes, the code inside the mergeInventoryPerItemNumber() becomes concise and easier to comprehend. Collectors.groupingBy() is used to create a map by grouping items with the same itemNumber. A downstream collector Collectors.reducing() is used to combine items grouped under the same key to a single object.

Note that Collectors.reducing() produces an Optional result. Therefore, filter(Optional::isPresent) is used as a precaution to make sure that the result exists and subsequent operation map(Optional::get) extracts the item from the optional 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());
}

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);
}

Output

Item{itemNumber='EU100', subtypes=[FRESH], quantity=100}
Item{itemNumber='EU999', subtypes=[FRESH, DAIRY], quantity=1860}
千と千尋 2025-01-20 02:00:23

也许可以通过一次扫描来完成此操作,但在这里我通过两次传递解决了这个问题:一次将类似的项目分组在一起,另一次在每个组中的项目上构建一个代表性项目(这在精神上似乎与您的类似)代码,您还尝试从组中传输元素)。

   
    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());
            }
        }
    }

It may be possible to do this with a single sweep, but here I have solved it with two passes: one to group like items together, and another over the items in each group to build a representative item (which seems similar in spirit to your code, where you were also attempting to stream elements from groups).

   
    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());
            }
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文