Ниже приведен код, который подсчитывает количество вхождений чисел в потоке и возвращает карту с числом в качестве ключа и значением в виде его вхождений в потоке:
Map<Integer, Long> collect = Stream.of(1, 2, 3, 4, 4, 55555, 12)
.collect(groupingBy(Function.identity(), counting()));
Как ограничить результирующую карту только наибольшим числом (или числами в случае ничьей)?




Я предполагаю, что вы хотите получить самые частые числа в своем потоке. Поэтому вы можете использовать TreeMap, собирая все результаты и получая список последней записи:
Map<Integer, Long> collect = Stream.of(1, 2, 3, 4, 4, 55555, 12)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue, TreeMap::new, Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
.lastEntry().getValue();
Это возвращает список записей, содержащих число и частоту. В вашем случае он печатает:
{4=2}
Если вы просто хотите получить количество вхождений самого большого числа, вы можете использовать это:
Map.Entry<Integer, Long> collect = Stream.of(1, 2, 3, 4, 4, 55555, 12)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByKey())
.orElse(null);
Что печатает:
55555=1
В последнем случае у вас есть только одна (максимальное значение) возвращаемая запись. Вы также можете использовать TreeMap, чтобы получить максимальное значение, должно иметь лучшую производительность.
Я надеюсь, что кто-то может придумать более простое решение:
List<Entry<Integer, Long>> list =
List.of(3, 3, 4, 4, 5, 5, 1)
.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.<Integer, Long>comparingByValue().reversed())
.collect(Collectors.toList());
Map<Integer, Long> result = new HashMap<>();
Iterator<Entry<Integer, Long>> iter = list.iterator();
Entry<Integer, Long> left;
Entry<Integer, Long> right = null;
while (iter.hasNext()) {
left = iter.next();
if (right == null) {
result.put(left.getKey(), left.getValue());
}
if (iter.hasNext() && (right = iter.next()).getValue().longValue() == left.getValue()) {
result.put(right.getKey(), right.getValue());
} else {
break;
}
}
Поэтому сначала соберите их на ту же карту, которая у вас уже есть. Затем отсортируйте их по значению; затем повторите этот результат и получите Только те, которые находятся в самом начале и соответствуют их значениям.
Идея состоит в том, что, поскольку они уже отсортированы по значению: 3 = 2; 4 = 2; 5 = 2; 1 = 1 - нам нужно повторять только до тех пор, пока это 2 повторяется, как только такого совпадения нет, мы закончили (поскольку они отсортированы и, следовательно, любой следующий элемент и != 2 будет только меньше).
@SamuelPhilipp, вы пробовали это с образцом, который у меня есть?
Мое решение возвращает [3=2, 4=2, 5=2]. Разве это не правильно?
@SamuelPhilipp это так, я пропустил ваш ответ, но не забывайте, что в результате OP было Map
Взгляните на этот простой пример:
public static void getMeNumbersWithHighestFrequence3(int[] numbers, int howMany) {
Map<Integer, Long> collect = IntStream.of(numbers).boxed().collect(groupingBy(Function.identity(), TreeMap::new, counting())).descendingMap().entrySet().stream()
.limit(howMany)
.collect(TreeMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), Map::putAll);
}
Вы также можете каким-то образом искать их, указав значение фильтра, и он будет принимать все записи со значениями ключа, превышающими это значение, например:
public static void getMeNumbersWithHighestFrequenceByFilterNumber(int[] numbers, int value) {
Map<Integer, Long> collect = IntStream.of(numbers).boxed().collect(groupingBy(Function.identity(), TreeMap::new, counting())).descendingMap().headMap(value, true);
}
Simple usage:
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 4, 55555, 12};
getMeNumbersWithHighestFrequence(numbers, 5);
}
Я бы посоветовал вам использовать MoreCollectors, определенный в StreamEx или моей библиотеке счет-утилита:
int result = Stream.of(1, 2, 12, 3, 4, 4, 55555, 12)
.collect(MoreCollectors.maxAll(MoreCollectors.countingInt()));
// map result
Map<Integer, Integer> mapResult = Stream.of(1, 2, 12, 3, 4, 4, 55555, 12)
.collect(maxAll(groupingBy(Function.identity(), countingInt())));
Потому что вы можете захотеть: 1) суммировать все самые большие числа, или 2) сопоставить их с чем-то другим, или 3)... Больше. Не нужно и не следует писать такие специальные коды только для одного конкретного случая пользователя. (если вы хотите знать, как это реализовать, просто скачайте исходный код библиотек или декомпилируйте класс. Они опубликованы в Apache License v2)
Обновлять. На самом деле я думаю, что это неправильный вопрос, если вы говорите о числах, потому что традиционный for-loop намного проще и эффективнее, чем использование лямбда-выражений:
int[] nums = {1, 2, 12, 3, 4, 4, 55555, 12, 55555};
int[] result = {Integer.MIN_VALUE, 0}; // [0] is the largest number if [1] (occurrence) is bigger than 0.
for (int num : nums) {
if (num > result[0]) {
result[0] = num;
result[1] = 1;
} else if (num == result[0]) {
result[1]++;
}
}
System.out.println(result[0] + ": " + result[1]);
если вам нужно использовать Stream/Lambdas:
int[] result = IntStream.of(nums).collect(() -> new int[] {Integer.MIN_VALUE, 0}, (a, num) -> {
if (num > a[0]) {
a[0] = num;
a[1] = 1;
} else if (num == a[0]) {
a[1]++;
}
}, (a1, a2) -> {
if (a1[0] == a2[0]) {
a1[1] += a2[1];
} else if (a1[0] < a2[0]) {
a1[1] = a2[1];
}
});
System.out.println(result[0] + ": " + result[1]);
Разве мое решение не проще?