4

I have a stream of elements and I want transform it to another stream that would consist only of elements that appeared the most in the previous stream assuming that I may have multiple elements with the same number of occurrences.

As an example:

Input stream: ['A', 'B', 'A', 'B', 'C', 'C', 'A', 'B']

Output stream: ['A', B'] as they both had max number of occurrences in the previous stream equal to 3.

So far I've written the following code:

stream.collect(toMap(w -> w, w -> 1, Integer::sum))
                .entrySet()
                .stream()
                .max(comparing(Map.Entry::getValue))
                .map(Map.Entry::getKey);

But this allows me to get only one of the elements with the max number of occurrences and for the case above it would give either 'A' or 'B'.

Is there any elegant way of how this can be achieved using lambdas?

Thanks, Cheers

Andrey Yaskulsky
  • 2,458
  • 9
  • 39
  • 81
  • 1
    This is not streaming as you, by definition, need to see the final element before emitting the first downstream. Pretending that you're streaming gives you nothing. – Boris the Spider Jun 02 '18 at 19:14
  • 3
    Seems to be a specific case of [How to force max() to return ALL maximum values in a Java Stream?](https://stackoverflow.com/q/29334404/2711488) – Holger Jun 03 '18 at 09:21

2 Answers2

5

There's no easy way to do it in one go, but you can sort the map to find the maximum value, or just use IntStream::max to find it, and then filter the map to find all entries matching that value:

var map = Stream.of('A', 'B', 'A', 'B', 'C', 'C', 'A', 'B')
    .collect(toMap(w -> w, w -> 1, Integer::sum));

int max = map.values().stream()
    .mapToInt(n -> n)
    .max().orElse(0);

map.entrySet().stream()
    .filter(e -> max == e.getValue())
    .forEach(System.out::println);

Output:

A=3
B=3
David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • 2
    Nice! although I would use `.max().orElse(0);` or `.max().orElse(-1);` for safety instead of `.max().getAsInt();` – Ousmane D. Jun 02 '18 at 18:39
  • 1
    since you're only interested in the values to find the max you could do `map.values().stream().mapToInt(Integer::intValue)...` instead of streaming over the entrySet. as an aside, one could also do `int max = Collections.max(map.values());` to find the max element. The only issue now is if the source is empty then an exception is thrown. – Ousmane D. Jun 02 '18 at 18:51
  • 1
    @Aominè as it is, if the source is empty you simply get no output, but using `values().stream().mapToInt(n -> n)` is cleaner than what I have. I got so into thinking about `entrySet()` that I didn't even think of `values()`. – David Conrad Jun 02 '18 at 18:55
  • @Andrey This answer is better than mine. – Oleksandr Pyrohov Jun 02 '18 at 19:37
1

In the first place, why do you start with a Stream<Character>. You can start with an array of chars and then use the iteration to build up a map from char to count and in the meantime calculate the max value in the map while iterating. Then use this map and the max value intermediate result to get the final result. This approach just passes over the collection only twice. Here's how it looks given that you have a char[] named chars upfront.

final Map<Character, Integer> charCntMap = new HashMap<>();
int maxCnt = 0;
for (char c : chars) {
    charCntMap.merge(c, 1, (a, b) -> a + b);
    final int charCnt = charCntMap.get(c);
    if (maxCnt < charCnt)
        maxCnt = charCnt;
}

final int maxVal = maxCnt;

List<Character> maxOccuringChars = charCntMap.entrySet().stream()
    .filter(e -> e.getValue().intValue() == maxVal)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

Output:

[A, B]
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
  • 1
    you should use `Objects.equals` instead of `==` as `entry.getValue()` is a reference type and so is the result from `max`, or you can avoid using `Objects.equals` with a little change i.e. by using `.entry.getValue().longValue()` instead of `entry.getValue()`. – Ousmane D. Jun 03 '18 at 11:28
  • 1
    your code only works now because the data in the `chars` list is small. so, it doesn't _gurantee_ that it will always work. see [this answer](https://stackoverflow.com/questions/20541636/compare-non-primitive-long-values-127-and-128) – Ousmane D. Jun 03 '18 at 11:34
  • @OusmaneD. Please see my last edit. Thanks for the feedback. Appreciate it. – Ravindra Ranwala Mar 30 '20 at 07:36