This week's book giveaway is in the Agile/Processes forum.
We're giving away four copies of Building Green Software: A Sustainable Approach to Software Development and Operations and have Anne Currie, Sarah Hsu , Sara Bergman on-line!
See this thread for details.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Tim Cooke
  • Campbell Ritchie
  • paul wheaton
  • Jeanne Boyarsky
  • Ron McLeod
Sheriffs:
  • Paul Clapham
  • Devaka Cooray
Saloon Keepers:
  • Tim Holloway
  • Carey Brown
  • Piet Souris
Bartenders:

how to write this more elegantly to merge a list of maps

 
author & internet detective
Posts: 42109
934
Eclipse IDE VI Editor Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have a bunch of Maps that map a String key to a List of Integers. I want to merge them so that I have one map with String key and the union of all those integers.

For example, suppose I have these two maps in my list:
map 1:
{
"a": [1],
"b" : [2, 3]
}

map 2:
{
"b" : [3, 4],
"c" : [5]
}

The result would be
{
"a": [1],
"b" : [2, 3,3, 4],
"c" : [5]
}


And the method signature is:


I have working code that involves two loops and the Map's compute() method.

This feels inelegant though. Is there a better way?
 
Saloon Keeper
Posts: 5603
214
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
A possible way, but also not very elegant, is:

Beware of using the 'of' versions of Map and List: these lead to immutable lists and maps, so methods like 'merge' fail.
 
Bartender
Posts: 2911
150
Google Web Toolkit Eclipse IDE Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I did this :

 
salvin francis
Bartender
Posts: 2911
150
Google Web Toolkit Eclipse IDE Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
My colleague suggested that if you are okay to modify the original map, here's a solution that changes the first map:
 
Sheriff
Posts: 22839
132
Eclipse IDE Spring Chrome Java Windows
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

salvin francis wrote:


If you want to merge to streams, you can use Stream.concat:
That saves you the memory usage of the list and the time needed to add all map entries. It might not be a significant gain if your maps are small, but if they're large - oh boy!
And if you have more than two streams, you can use Stream.of, Arrays.stream or Collection.stream to create a Stream of Streams, and then flatMap it using Function.identity().

And you should consider using a Collector. I think that groupingBy in combination with the right (possibly custom) downstream Collector will give the correct results.
 
Piet Souris
Saloon Keeper
Posts: 5603
214
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I did consider (and used) a Collector. And although 'de gustibus non est disputandem', I found my merge solution more elegant. I had this: (assuming 'maps' is Jeanne's List<Map<String, List<Integer>>>)

Interesting is that if you write 'var flup = maps.stream()...' then you get a Compiler error that Entry::getKey is incorect, and you may get a Runtime error if your maps contain unmodifiable elements (for instance if you use List.of(1, 2, 3). The errors are such that it takes a long time to figure out what is going on.

@Salvin
in your first code snippet, take care that there may be more than only two maps involved
 
Greenhorn
Posts: 27
1
IBM DB2 Netbeans IDE Chrome Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Jeanne,

This was a fun exercise and very challenging. Below is what I came up with. I don't know if it is much more elegant but I finally got it to output what you were looking for.
Hopefully it would be some use to you.

The BiFunction (mergeList) at the beginning of the method is used to merge the lists if there is a duplicate key.

 
Jeanne Boyarsky
author & internet detective
Posts: 42109
934
Eclipse IDE VI Editor Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks all. While I'm sticking with my original code*, I learned a lot here!

I find it interesting that Piet's is most similar to mine. I used traditional lists instead of forEach() because I was wary of side effects in the stream. OVerall,  of the solutions are things I didn't think of making this quite educational!

* This needs to be readable for my teammates who are less comfortable with advanced streams.
 
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Late to the party...  

The Map#compute documentation seems to suggests that Map#merge is simpler so I gave it a shot:

I don't know if that's necessarily more elegant or even simpler than what you came up with, Jeanne.

You can test this code here: https://repl.it/@jlacar/ListMerge

EDIT: Got some code review feedback (Thanks, Rob!). Also added version that used .forEach() like Piet did but I opted to avoid side effects. The code using .forEach() looks cleaner to me.

 
Greenhorn
Posts: 4
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
What do you think about that?
 
Bartender
Posts: 15737
368
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Welcome to CodeRanch Alex!

You can probably simplify your collection step with a groupingBy(), rather than a toMap().
 
Aleksandr Shitov
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Welcome to CodeRanch Alex!

You can probably simplify your collection step with a groupingBy(), rather than a toMap().



Won't be work unfortunately, because there is no appropriate mapper for groupingBy function to flat grouped lists to one.
 
Piet Souris
Saloon Keeper
Posts: 5603
214
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Many different solutions! It shows that the Java 8(+) streams and lambdas are a lot of fun to use.

@Jeanne
I found this remrk interesting:

Jeanne Boyarsky wrote:* This needs to be readable for my teammates who are less comfortable with advanced streams.


Can you tell me in how far the java 8(+) streams and lambdas are used in practice, compared to the java < 8 ways?
 
Rob Spoor
Sheriff
Posts: 22839
132
Eclipse IDE Spring Chrome Java Windows
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:      for (String key : m.keySet())
       result.merge(key, m.get(key), (t, u) -> {[/code]


Whenever I see someone looping over the key set and then using get, I am forced to tell them to use entrySet instead...
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Rob Spoor wrote:

Junilu Lacar wrote:      for (String key : m.keySet())
       result.merge(key, m.get(key), (t, u) -> {[/code]


Whenever I see someone looping over the key set and then using get, I am forced to tell them to use entrySet instead...



Yes, that's a good point.  

I made the appropriate modifications to the code on repl.it
 
Piet Souris
Saloon Keeper
Posts: 5603
214
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Alex Shitov wrote:

Stephan van Hulst wrote:Welcome to CodeRanch Alex!

You can probably simplify your collection step with a groupingBy(), rather than a toMap().



Won't be work unfortunately, because there is no appropriate mapper for groupingBy function to flat grouped lists to one.


Well, it CAN be done, but it is not the most simple solution...

I tried to indent it so, that parts that go together are nicely grouped.

Of course I did try to make this more generic, something like

but for the time being I get a load of errors....
 
Piet Souris
Saloon Keeper
Posts: 5603
214
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hmmm, I start to belive that Alex Shitov is right, after all. My above code does not work; I added a third Map, and the reducing reduced the complete value Collection of all Maps, no matter what key it was.
Does anyone know what I am doing wrong?

Output:

******** The combined listt of the entries************
a=[1, 2, 3]
a=[2, 3, 4]
b=[10]
a=[3, 4, 5]
******************************************************
****** the final map, incorrect ************
a=[1, 2, 3, 2, 3, 4, 10, 3, 4, 5]
b=[1, 2, 3, 2, 3, 4, 10, 3, 4, 5]
 
Jeanne Boyarsky
author & internet detective
Posts: 42109
934
Eclipse IDE VI Editor Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I like that Alex's attempt doesn't use a loop!

Piet Souris wrote:@Jeanne

Jeanne Boyarsky wrote:* This needs to be readable for my teammates who are less comfortable with advanced streams.


Can you tell me in how far the java 8(+) streams and lambdas are used in practice, compared to the java < 8 ways?


They are good with the basics: stream/filter/map/collect. But writing a custom Collector goes way to far. Also, if it isn't simpler than the loop solution...
 
Aleksandr Shitov
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Piet Souris wrote:Hmmm, I start to belive that Alex Shitov is right, after all. My above code does not work; I added a third Map, and the reducing reduced the complete value Collection of all Maps, no matter what key it was.
Does anyone know what I am doing wrong?

Output:

******** The combined listt of the entries************
a=[1, 2, 3]
a=[2, 3, 4]
b=[10]
a=[3, 4, 5]
******************************************************
****** the final map, incorrect ************
a=[1, 2, 3, 2, 3, 4, 10, 3, 4, 5]
b=[1, 2, 3, 2, 3, 4, 10, 3, 4, 5]



This is because ArrayList in Collectors.reducing(new ArrayList<>(), ...) is created only once and used for each grouped key as initial(zero-size) list. Fixed version of compressMap2(...):



Thanks for evaluation)
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Seeing all these variations is great from a learning standpoint but I can see why Jeanne is deciding to stick with her two-loop (and probably) more imperative version. Given my own attempt at a solution, I think I would prefer that over the more "streamy" solutions suggested. I also like the conciseness of Alex's solution but the flatmap() method is something that always seems to twist my brain in knots whenever I encounter it, which isn't that often to begin with.
 
Stephan van Hulst
Bartender
Posts: 15737
368
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


Alex' version may be preferable, because it creates a new stream object per list, while my version creates a new entry object per list element.
 
Jeanne Boyarsky
author & internet detective
Posts: 42109
934
Eclipse IDE VI Editor Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:Seeing all these variations is great from a learning standpoint but I can see why Jeanne is deciding to stick with her two-loop (and probably) more imperative version.


Yup. I'm glad I posted for learning though!
 
With a little knowledge, a cast iron skillet is non-stick and lasts a lifetime.
reply
    Bookmark Topic Watch Topic
  • New Topic