Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge List of Maps of Maps into a Map of Maps?

Could you help me with Java Streams?

As you can see from the title I need to merge List<Map<String, Map<String, Genuineness>>> into Map<String, Map<String, Genuineness>>.

The list is represented as List<Map<String, Map<String, Genuineness>>> and looks like:

[  
   {  
      "USER_1":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         },
         "APP_2":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      }
   },
   {  
      "USER_1":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":1,
            "totalGenuine":1,
            "totalDevelopment":1
         },
         "APP_2":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      }
   }
]

So, as you can see, duplicate keys could be everywhere. My goal is to combine them into Map<String, Map<String, Genuineness>> by merging Genuineness. Merge Genuineness simply means return a new object with summed up values total, totalGenuine, and totalDevelopment.

Here is my implementation that fails:

final Map<String, Map<String, Genuineness>> map = taskHandles.stream().map(this::mapTaskHandle)
                .flatMap(m -> m.entrySet().stream()).collect(
                        Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
                                .collect(
                                        Collectors.toMap(Map.Entry::getKey,
                                                g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
                                                (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
                                                        g1.getTotalGenuine() + g2.getTotalGenuine(),
                                                        g1.getTotalDevelopment() + g2.getTotalGenuine()
                                                )
                                        )
                                )
                        )
                );

It fails with message:

java.lang.IllegalStateException: Duplicate key {TEST_33_33_APP_1=live.attach.billing.domain.model.billing.Genuineness@951b6fe}

So, seems that in my implementation I've pointed how to combine inner map but didn't make a merging of values of outer map and I don't know how to do it.

I greatly appreciate your help. Thank you in advance!

UPDATE: Expected output:

   {  
      "USER_1":{  
         "APP_1":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         }
      },
      "USER_2":{  
         "APP_1":{  
            "total":2,
            "totalGenuine":2,
            "totalDevelopment":2
         },
         "APP_2":{  
            "total":4,
            "totalGenuine":4,
            "totalDevelopment":4
         }
      }
   }
like image 705
Pasha Avatar asked Nov 30 '18 18:11

Pasha


People also ask

How do I merge a list of maps?

forEach((k, v) -> mapGlobal. merge(k, v, (v1, v2) -> { Set<String> set = new TreeSet<>(v1); set. addAll(v2); return new ArrayList<>(set); })); If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it.

What is merge method in maps?

Java HashMap merge() The Java HashMap merge() method inserts the specified key/value mapping to the hashmap if the specified key is already not present. If the specified key is already associated with a value, the method replaces the old value with the result of the specified function.


2 Answers

Honestly, this is a horrible data structure to work with and the maintainers of this code will have a hard time finding out problems that arise.

You should take a step back and consider refactoring the code, any way you can solve the missing part by using the following merge function in the outmost toMap:

(l, r) -> {
      r.forEach((k, v) -> l.merge(k, v,
                    (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
                               bi.getTotalGenuine() + bii.getTotalGenuine(),
                               bi.getTotalDevelopment() + bii.getTotalGenuine())));
       return l;
}

Full code:

taskHandles.stream().map(this::mapTaskHandle)
                .flatMap(m -> m.entrySet().stream()).collect(
                        Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
                                .collect(
                                        Collectors.toMap(Map.Entry::getKey,
                                                g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
                                                (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
                                                        g1.getTotalGenuine() + g2.getTotalGenuine(),
                                                        g1.getTotalDevelopment() + g2.getTotalGenuine()
                                                )
                                        )
                                ),(l, r) -> {
                                  r.forEach((k, v) -> l.merge(k, v,
                                          (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
                                                  bi.getTotalGenuine() + bii.getTotalGenuine(),
                                                  bi.getTotalDevelopment() + bii.getTotalGenuine())));
                                  return l;
                                }

                        )
                );

Ideone

like image 179
Ousmane D. Avatar answered Oct 09 '22 10:10

Ousmane D.


Though Aomine's suggested solution seems to be correct, you can alternatively improve your code readability and simplify it defining a BinaryOperator<Genuineness> as :

BinaryOperator<Genuineness> remappingGenuineness = (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
        g1.getTotalGenuine() + g2.getTotalGenuine(),
        g1.getTotalDevelopment() + g2.getTotalGenuine()
);

and then further use it as :

final Map<String, Map<String, Genuineness>> map = taskHandles.stream()
        .flatMap(m -> m.entrySet().stream())
        .collect(Collectors.toMap(Map.Entry::getKey,
                e -> e.getValue().entrySet().stream().collect(
                        Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, remappingGenuineness)),
                (a, b) -> {
                    a.forEach((k, v) -> b.merge(k, v, remappingGenuineness));
                    return b;
                }));
like image 39
Naman Avatar answered Oct 09 '22 10:10

Naman