2

examples i have seen for finding mode using stream api return a single mode and if there are two elements that occur equally, the first is returned. however, returning an array with both (or more than 2) elements i can find no example of. i am not sure if there is a simple tweak of the following code:

Integer mode = list.stream()
            .collect(Collectors.groupingBy(i -> i, () -> new TreeMap<Integer, Long>(), Collectors.counting()))
            .entrySet().stream().sorted((a, b) -> {
                if (!a.getValue().equals(b.getValue()))
                    return b.getValue().compareTo(a.getValue());
                return a.getKey().compareTo(b.getKey());
            }).findFirst().get().getKey();

Instead of Integer mode I am trying to get Integer[] mode or List<Integer> mode.

Naman
  • 27,789
  • 26
  • 218
  • 353
releseabe
  • 323
  • 3
  • 13

2 Answers2

2

I suggest to invert the frequency Map, and get the last entry of the inverted Map:

List<Integer> modes = 
    list.stream()
        .collect(Collectors.groupingBy(Function.identity(), 
                                       Collectors.counting())) // Map<Integer,Long>
        .entrySet()
        .stream()
        .collect(Collectors.groupingBy(Map.Entry::getValue, // TreeMap<Long,List<Integer>>
                                       TreeMap::new,
                                       Collectors.mapping(Map.Entry::getKey,
                                                          Collectors.toList())))
        .lastEntry()
        .getValue(); // you want the last value of the TreeMap

Sample Input:

List<Integer> list = Arrays.asList(1,2,3,4,3,2,4,5,6);

Output List:

[2, 3, 4]

Notes:

  1. The first Map doesn't have to be a TreeMap, since you don't care about the order of its keys.

  2. By making the second Map a TreeMap, the ordering is taken care of, and you don't need to use sorted().

Eran
  • 387,369
  • 54
  • 702
  • 768
2

This is a special variant of the question How to force max() to return ALL maximum values in a Java Stream?

The simplest solution is to identify the maximum value first and filter all elements, keeping those matching the maximum value:

Map<Integer, Long> frequencies = list.stream()
        .collect(Collectors.groupingBy(i -> i, Collectors.counting()));

long maxCount = frequencies.values().stream().mapToLong(Long::longValue).max().orElse(0);

List<Integer> all = frequencies.entrySet().stream()
    .filter(e -> e.getValue() == maxCount)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

Alternatively, you can use the collector of this answer to get all maximum elements in a single stream operation, which allows to chain it to the first operation, as you don’t need to access the frequency map twice:

List<Integer> all = list.stream()
    .collect(Collectors.groupingBy(i -> i, Collectors.counting()))
    .entrySet().stream()
    .collect(maxAll(Map.Entry.comparingByValue(),
        Collectors.mapping(Map.Entry::getKey, Collectors.toList())));

But this is only simpler if you don’t count the collector implementation, i.e. if you add it permanently to your code base because you need it more often.

Holger
  • 285,553
  • 42
  • 434
  • 765