简体   繁体   中英

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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