0

I have a int[] converted to a List<Integer>. The task at hand is to map each element of the set to 0. So I think of converting this List to a Set and then map to 0. So for a set 1, 3, 7, 4, 8, it should look like: (1, 0), (3, 0), (7, 0), (4, 0), (8, 0). To achieve this, I've tried:

Map<Integer, Integer> map = Arrays.stream(elements)
    .boxed()
    .collect(Collectors.toMap(x -> x, x -> 0));

But while trying this, I get this error:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 0
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1254)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.stream.IntPipeline$4$1.accept(IntPipeline.java:250)
    at java.util.Spliterators$IntArraySpliterator.forEachRemaining(Spliterators.java:1032)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at DeleteElementsByOccurence.main(DeleteElementsByOccurence.java:18)                  <-- Refers to the Collectors.toMap line

I know this stuff is super easy in Scala, but I am unable to figure out the logic to do in Java.

Sparker0i
  • 1,787
  • 4
  • 35
  • 60
  • Is it actually a Set? Your code implies an array of elements, and that's not a set. Check if there are duplicate elements in your array. – M. Prokhorov Mar 02 '20 at 17:54
  • My bad, it's a list – Sparker0i Mar 02 '20 at 17:55
  • 2
    Then what is supposed to happen if there are duplicate numbers in a list? Is it `[1,1,2] -> [(1,0), (1,0), (2,0)]` or `[1,1,2] -> [(1,0), (2,0)]`? – M. Prokhorov Mar 02 '20 at 17:56
  • 2
    You shouldn't be able to use `Arrays.stream` if you have a `List` though. Do you actually have a `int[]`? – Sweeper Mar 02 '20 at 17:59
  • `[1,1,2] -> [(1,0), (2,0)]` – Sparker0i Mar 02 '20 at 17:59
  • 2
    @Sparker0i, then it's `.distinct()`, or first make a set, then stream the set. There are already answers for most viable approaches. Note that `.distinct()` also has a set under the hood, and `toMap` with three lambdas doesn't create a set, and still works as you'r expect in the end. – M. Prokhorov Mar 02 '20 at 18:04
  • Does this answer your question? [Ignore duplicates when producing map using streams](https://stackoverflow.com/questions/32312876/ignore-duplicates-when-producing-map-using-streams) – Vishwa Ratna Mar 03 '20 at 06:30

2 Answers2

3

elements seem to contain duplicates. To deal with this, you can supply a third argument to toMap to say what you want to do when there are duplicates. (this overload)

.collect(Collectors.toMap(x -> x, x -> 0, (x, y) -> x));

x and y are the values corresponding to two duplicate keys, and you get to decide what the new value is for that key. Since in this case all the values are 0, there's not much of a choice, is there?

You don't actually need to convert the array to a set first, because the third argument (merger function) handles duplicates automatically.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 1
    This solution is marginally better than `.distinct()` one, because it will not allocate a `Set` under the hood to keep track of seen elements, so a tiny bit less object allocation and none of the set lookups. – M. Prokhorov Mar 02 '20 at 18:08
  • @M.Prokhorov You mean marginally *faster*. Faster != better. – Michael Mar 02 '20 at 18:11
  • 1
    @Michael, not only faster, but also uses less memory intermediately. The solution which involves the `Set`s is better because it's a little more logically consistent. Merger lambda is technically a trick. – M. Prokhorov Mar 02 '20 at 18:14
  • 1
    And the misleading exception message has been discussed in [Why does Collectors.toMap report value instead of key on Duplicate Key error?](https://stackoverflow.com/q/40039649/2711488) – Holger Mar 03 '20 at 10:33
3

If your list contains duplicate keys, the map will throw an exception. You can use distinct() to remove the duplicate keys.

int[] a = {1,3,7,4,4,8}; // 4 is duplicated

Map<Integer, Integer> map = Arrays.stream(a)
    .boxed()
    .distinct()   // added
    .collect(Collectors.toMap(x -> x, x -> 0));

Alternatively, pass a merge function

Map<Integer, Integer> map = Arrays.stream(a)
    .boxed()
    .collect(Collectors.toMap(x -> x, x -> 0, (x, y) -> x));

(equally correct, identical merge functions: (x, y) -> y, (x, y) -> 0)

Michael
  • 41,989
  • 11
  • 82
  • 128