How to merge two arrays into a map using Java streams?
Asked Answered
Z

4

8

Lets suppose we were given the following two arrays

String[] keys   = new String[] {"a", "b", "c", "aa", "d", "b"};
int[]    values = new int[]    { 1 ,  2 ,  3 ,  4  ,  5 ,  6 };

And by merging these 2 arrays into a HashTable we get the following

// pseudo-code
Map<String, Integer> dictionary = new HashTable<>(
  ("a"  => 1)
  ("b"  => 8) // because "b" appeared in index 1 and 5
  ("c"  => 3)
  ("aa" => 4)
  ("d"  => 5)
);

How can we do this using Java lambda style?

So far I have the following:

// this loops through the range given (used for index start and end)
// and sums the values of duplicated keys
tree.listMap = IntStream.range(range[0], range[1]).boxed().collect(
  Collectors.toMap(i - > dictionary[i], i - > i + 1, Integer::sum, TreeMap::new)
);

However, I'd like to take 2 arrays, merge them by key and value, where value is the sum of all values for duplicated keys. How can we do this?

Zinc answered 26/10, 2017 at 7:30 Comment(0)
S
9

There you go:

Map<String,Integer> themap = 
       IntStream.range (0, keys.length).boxed()
                .collect (Collectors.toMap(i->keys[i],
                                           i->values[i],
                                           Integer::sum,
                                           TreeMap::new));

Output:

{a=1, aa=4, b=8, c=3, d=5}

This is quite similar to the snippet you posted, though, for some reason, the snippet you posted contains no reference to the keys and values arrays.

Secular answered 26/10, 2017 at 7:39 Comment(4)
There is a slight difference in the output of this and this. Just curious to know, does the groupingBy sorts the data as well?Doubly
@nullpointer Map makes no guarantees about iteration order so the output is for all intents and purposes the same.Dimerous
@nullpointer My answer returns a TreeMap (as in the OPs code), so the keys are sorted. You can also modify the groupingBy solution to return a TreeMap (by default, the current implementation returns a HashMap).Secular
Thanks Eran for your answer. I was kind of stuck on how we merge 2 separate arrays. Hence why you don't see any reference of keys and values. I have only obtained a single array prior to your solution.Zinc
D
3

I don't like using streams when referring to indexes, but you can use groupingBy and summingInt to accomplish this:

Map<String, Integer> result = IntStream.range(0, keys.length)
   .boxed()
   .collect(
       Collectors.groupingBy(
           i -> keys[i],
           Collectors.summingInt(i -> values[i])
       )
   );

Note that this works on the assumption that keys and values are both of equal length, so you may want to do some additional validation.

Dimerous answered 26/10, 2017 at 7:42 Comment(2)
I didnt give too much focus towards sorted map. The requirement was simply to merge two arrays with given values as sum for duplicated keys. Have I been too vague in my question? Let me know and I'll edit. Regardless Michael, I appreciate your answer. It was very warming to learn something new :)Zinc
@Zinc Nope, your question was perfectly clear. Happy to help.Dimerous
T
0

The Google Guava library has the Streams.zip method, which will zip two streams. The zipped stream can then be converted to a map using Collectors.toMap, using Integer.sum as the merge function to add duplicate key values together.

Map<String, Integer> dictionary =
        Streams.zip(Arrays.stream(keys), Arrays.stream(values).boxed(), Map::entry)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
Tacit answered 18/6 at 20:5 Comment(0)
T
0

The StreamEx library has the zipWith method, which will zip two streams into a StreamEx EntryStream. They can then be converted into a Map using EntryStream.toMap, using Integer.sum as the merge function to add duplicate key values together.

Map<String, Integer> dictionary =
        StreamEx.of(keys).zipWith(IntStreamEx.of(values)).toMap(Integer::sum);
Tacit answered 18/6 at 20:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.