Search code examples
javadictionarycollectionsjava-streamcollectors

How to merge two Maps based on values with Java 8 streams?


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

Solution

  • 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}