3

Map is defined as:

 Map<Integer,String> map = new HashMap<>();
        map.put(2,"ram");
        map.put(3,"ram");
        map.put(4,"gopal");
        map.put(5,"madan");
        map.put(6,"shyam");
        map.put(7,"gopal");
        map.put(8,"ram");

My expected output is List which contains only keys for which there is no duplicate values.

5
6

My approach and thought process:

Thought process 1 :

I would take map.entrySet().stream().map(....) and then take another stream inside map and filter the values for which duplicate values are present.

The approach soon got wasted as the first indexed value would be again compared in the nested stream and i would happen to filter out all elements thus.

Thought process 2

I kept values in different List by :

List<String> subList = map.entrySet().stream()
        .map((k)->k.getValue())
        .collect(Collectors.toList());

and then:

    map.entrySet().stream()
            .filter(s ->
                subList.contains(s.getValue())                )
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

But I am getting the output as

2
3
4
5
6
7
8

The output is obvious as the value that i am picking as s from stream i am comparing it in the pool where the value will be always present at-least one time.

I again thought then that if i could have a counter that count count and if the value is present then it would increment, but again all seems very much vague now.

Any ways in which i can iterate by index using stream, so that i can always leave the key value which i am taking and just comparing with rest of values.

will love to get a brief explanation.

Naman
  • 27,789
  • 26
  • 218
  • 353
Vishwa Ratna
  • 5,567
  • 5
  • 33
  • 55

2 Answers2

1

You can filter the values with frequency 1 while creating the subList such as:

Set<String> uniqueSet = map.values().stream()
        .collect(Collectors.groupingBy(a -> a, Collectors.counting()))
        .entrySet().stream()
        .filter(a -> a.getValue() == 1)
        .map((Map.Entry::getKey))
        .collect(Collectors.toSet());

and then perform the same operation as:

Set<Integer> result = map.entrySet().stream()
        .filter(e -> uniqueSet.contains(e.getValue()))
        .map(Map.Entry::getKey)
        .collect(Collectors.toSet());

Or as Holger pointed out in the comments, instead of counting, you could do away with a Boolean value to filter unique values as:

Set<String> uniqueSet = map.values().stream()
        .collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false))
        .entrySet().stream()
        .filter(Map.Entry::getValue)
        .map((Map.Entry::getKey))
        .collect(Collectors.toSet());
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 2
    Similar to [this comment](https://stackoverflow.com/questions/57819113/extract-keys-from-map-for-which-there-is-no-duplicate-values#comment102075967_57819323), you can replace `.collect(Collectors.groupingBy(a -> a, Collectors.counting())) .entrySet().stream() .filter(a -> a.getValue() == 1)` with `.collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false)) .entrySet().stream() .filter(Map.Entry::getValue)`. By the way, `subList` is a strange name for a `Set`. – Holger Sep 06 '19 at 13:30
  • @Holger Thank you. I could also recall another comment for `groupingBy` with `reduce` downstream to be replaced with `toMap` (not sure if it helps somehow finding an alternate solve for the question). On the naming part, I would definitely need to learn to spend more time thinking about what code to write than just writing it down I believe. – Naman Sep 06 '19 at 17:05
  • 1
    When you have a builtin collector like `counting()`, it can help reducing the boxing overhead in case of large groups. If you know beforehand that the groups won’t be large (compared to the number of groups), a reduction may be preferable, the same applies to the boolean solution, as the boxing overhead is negligible. And then, `toMap` is preferable to `groupingBy` with `reduce`, as discussed [in the Q&A](https://stackoverflow.com/a/57042622/2711488). I guess, the thing with names is, sometimes we start with something different in mind and after revising the code, we forget to adapt the names… – Holger Sep 09 '19 at 09:17
1

You can break the task into two step. first count value repeating by groupingBy() and counting() collectors.

Map<String,Long> valueCount = map.values()
           .stream()
           .collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));

Its result is:

{madan=1, shyam=1, gopal=2, ram=3}

second step is to find only keys which their values are not duplicate. so to attain this you can use filter() and filtering the map by previous step result.

map.entrySet()
   .stream()
   .filter(entry -> valueCount.get(entry.getValue())==1).map(Map.Entry::getKey)
   .collect(Collectors.toList())
Hadi J
  • 16,989
  • 4
  • 36
  • 62
  • 3
    You don’t need to count. A `Boolean` telling whether a value is unique, is enough: `Map unique = map.values().stream() .collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false));` Then, the subsequent filtering will also be a bit simpler: `map.entrySet().stream() .filter(entry -> unique.get(entry.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toList())` – Holger Sep 06 '19 at 13:26