Как заставить max возвращать ВСЕ максимальные значения в потоке Java?
Я немного протестировал max
функцию в Java 8 для лямбд и потоков, и кажется, что в случае выполнения max
, даже если более одного объекта сравниваются с 0, он возвращает произвольный элемент в связанных кандидатах без дальнейшего рассмотрения.
Есть ли очевидный трюк или функция для такого максимального ожидаемого поведения, чтобы возвращались все максимальные значения? Я ничего не вижу в API, но я уверен, что должно существовать что-то лучше, чем сравнение вручную.
Например:
// myComparator is an IntegerComparator
Stream.of(1, 3, 5, 3, 2, 3, 5)
.max(myComparator)
.forEach(System.out::println);
// Would print 5, 5 in any order.
Переведено автоматически
Ответ 1
Я полагаю, что OP использует Comparator
для разделения входных данных на классы эквивалентности, и желаемым результатом является список членов класса эквивалентности, который является максимальным в соответствии с этим Comparator
.
К сожалению, использование int
значений в качестве примера проблемы - ужасный пример. Все равные int
значения взаимозаменяемы, поэтому нет понятия сохранения порядка эквивалентных значений. Возможно, лучшим примером является использование string lengths , где желаемым результатом является возврат списка строк из входных данных, все из которых имеют наибольшую длину в пределах этого ввода.
Я не знаю ни одного способа сделать это без сохранения хотя бы частичных результатов в коллекции.
Учитывая входную коллекцию, скажем
List<String> list = ... ;
...это достаточно просто сделать за два прохода, первый для получения наибольшей длины, а второй для фильтрации строк с такой длиной:
int longest = list.stream()
.mapToInt(String::length)
.max()
.orElse(-1);
List<String> result = list.stream()
.filter(s -> s.length() == longest)
.collect(toList());
Если входными данными является поток, который нельзя обойти более одного раза, результат можно вычислить только за один проход с помощью сборщика. Написать такой сборщик несложно, но это немного утомительно, поскольку необходимо обработать несколько случаев. Вспомогательная функция, которая генерирует такой сборщик, учитывая Comparator
, выглядит следующим образом:
static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) {
return Collector.of(
ArrayList::new,
(list, t) -> {
int c;
if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) {
list.add(t);
} else if (c > 0) {
list.clear();
list.add(t);
}
},
(list1, list2) -> {
if (list1.isEmpty()) {
return list2;
}
if (list2.isEmpty()) {
return list1;
}
int r = comp.compare(list1.get(0), list2.get(0));
if (r < 0) {
return list2;
} else if (r > 0) {
return list1;
} else {
list1.addAll(list2);
return list1;
}
});
}
Промежуточные результаты сохраняются в ArrayList
. Неизменным является то, что все элементы в любом таком списке эквивалентны с точки зрения Comparator
. При добавлении элемента, если он меньше элементов в списке, он игнорируется; если он равен, он добавляется; и если он больше, список очищается и добавляется новый элемент. Объединение также не слишком сложно: возвращается список с большим количеством элементов, но если их элементы равны, списки добавляются.
Учитывая входной поток, это довольно просто в использовании:
Stream<String> input = ... ;
List<String> result = input.collect(maxList(comparing(String::length)));
Ответ 2
Я бы сгруппировал по значению и сохранил значения в a TreeMap
чтобы отсортировать мои значения, тогда я бы получил максимальное значение, получив последнюю запись в качестве следующей:
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(groupingBy(Function.identity(), TreeMap::new, toList()))
.lastEntry()
.getValue()
.forEach(System.out::println);
Вывод:
5
5
Ответ 3
Я реализовал более универсальное решение для сбора данных с помощью пользовательского нисходящего сборщика. Возможно, некоторым читателям это может оказаться полезным.:
public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BinaryOperator<A> downstreamCombiner = downstream.combiner();
class Container {
A acc;
T obj;
boolean hasAny;
Container(A acc) {
this.acc = acc;
}
}
Supplier<Container> supplier = () -> new Container(downstreamSupplier.get());
BiConsumer<Container, T> accumulator = (acc, t) -> {
if(!acc.hasAny) {
downstreamAccumulator.accept(acc.acc, t);
acc.obj = t;
acc.hasAny = true;
} else {
int cmp = comparator.compare(t, acc.obj);
if (cmp > 0) {
acc.acc = downstreamSupplier.get();
acc.obj = t;
}
if (cmp >= 0)
downstreamAccumulator.accept(acc.acc, t);
}
};
BinaryOperator<Container> combiner = (acc1, acc2) -> {
if (!acc2.hasAny) {
return acc1;
}
if (!acc1.hasAny) {
return acc2;
}
int cmp = comparator.compare(acc1.obj, acc2.obj);
if (cmp > 0) {
return acc1;
}
if (cmp < 0) {
return acc2;
}
acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc);
return acc1;
};
Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc);
return Collector.of(supplier, accumulator, combiner, finisher);
}
Таким образом, по умолчанию он может быть собран в список с помощью:
public static <T> Collector<T, ?, List<T>> maxAll(Comparator<? super T> comparator) {
return maxAll(comparator, Collectors.toList());
}
Но вы можете использовать и другие нижестоящие сборщики:
public static String joinLongestStrings(Collection<String> input) {
return input.stream().collect(
maxAll(Comparator.comparingInt(String::length), Collectors.joining(","))));
}
Ответ 4
Если я правильно понял, вам нужна частота max
значения в потоке.
Одним из способов добиться этого было бы сохранить результаты в TreeMap<Integer, List<Integer>
когда вы собираете элементы из потока. Затем вы берете последний ключ (или первый, в зависимости от предоставленного вами компаратора), чтобы получить значение, которое будет содержать список максимальных значений.
List<Integer> maxValues = st.collect(toMap(i -> i,
Arrays::asList,
(l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()),
TreeMap::new))
.lastEntry()
.getValue();
Собрав его из Stream(4, 5, -2, 5, 5)
, вы получитеList [5, 5, 5]
.
Другим подходом в том же духе было бы использовать операцию group by в сочетании с counting()
collector:
Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
TreeMap::new,
counting())).lastEntry(); //5=3 -> 5 appears 3 times
По сути, сначала вы получаете Map<Integer, List<Integer>>
. Затем нисходящий counting()
сборщик вернет количество элементов в каждом списке, сопоставленных по его ключу, что приведет к отображению. Оттуда вы берете запись max.
Первые подходы требуют сохранения всех элементов из потока. Второй подход лучше (см. Комментарий Хольгера), поскольку промежуточный List
не создан. В обоих подходах результат вычисляется за один проход.
Если вы получаете исходный код из коллекции, вы можете использовать Collections.max
один раз для поиска максимального значения, за которым следует Collections.frequency
, чтобы узнать, сколько раз это значение появляется.
Это требует двух проходов, но использует меньше памяти, поскольку вам не нужно создавать структуру данных.
Эквивалент потока будет coll.stream().max(...).get(...)
сопровождаться coll.stream().filter(...).count()
.