Возможны несколько вариантов решения:
- Сначала определяется максимальный элемент, а затем на втором проходе отфильтровываются значения, равные максимуму:
public static<T,R> List<R> maxList(Collection<T> data, Comparator<? super T> cmp, Function<T, R> converter) {
return data.stream().max(cmp) // определить максимум
.map(max -> data.stream() // второй проход
.filter(x -> cmp.compare(x, max) == 0) // фильтр максимумов
.map(converter::apply) // преобразование в нужный тип результата
.collect(Collectors.toList())
).orElseGet(Collections::emptyList);
}
- Использовать кастомный коллектор
Collector.of(Supplier<A> supplier, BiConsumer<A,T> accumulator, BinaryOperator<A> combiner, Function<A,R> finisher)
Здесь Supplier<A>
создаст списки, accumulator
отфильтрует максимумы, combiner
объединит несколько списков (в случае использования параллельных стримов), finisher
преобразует список List<T>
в список List<R>
при помощи функции-конвертера.
public static<T,R> List<R> maxList(Collection<T> data, Comparator<? super T> cmp, Function<T, R> converter) {
return data.stream()
.collect(Collector.of(
ArrayList::new,
(List<T> list, T i) -> {
if (list.isEmpty()) {
list.add(i);
} else {
int c = cmp.compare(i, list.get(0));
if (c >= 0) {
if (c > 0) list.clear();
list.add(i);
}
}
},
(lst1, lst2) -> {
if (lst1.isEmpty()) return lst2;
else if (lst2.isEmpty()) return lst1;
int c = cmp.compare(lst1.get(0), lst2.get(0));
if (c == 0) {
lst1.addAll(lst2);
return lst1;
}
return c > 0 ? lst1 : lst2;
},
lst -> lst.stream().map(converter::apply).collect(Collectors.toList())
));
}
- Сгруппировать по некоему ключу, получив хэш-таблицу, затем найти максимальное значение ключа.
В данном случае получится двойной проход (в худшем случае) -- сначала по входной коллекции, затем по сету групп,-- и генерация промежуточных списков для групп.
public static<T,M extends Comparable,R> List<R> maxListGroup(
Collection<T> data, Function<T, R> converter, Function<T, M> maxConverter
) {
Optional<Map.Entry<M, List<R>>> max = data.stream()
.collect(Collectors.groupingBy(
maxConverter,
Collectors.mapping(converter, Collectors.toList())
)) // Map<M, List<R>>
.entrySet()
.stream()
.max(Map.Entry.comparingByKey());
return max
.map(Map.Entry::getValue)
.orElseGet(Collections::emptyList);
}
Для поиска минимумов следует использовать Stream::min
в первом варианте, а во втором изменить знак в сравнениях с "больше" на "меньше".
Аналогично, если в дополнение к списку результатов нужно получить значение максимума, как в исходном вопросе, можно реализовать класс-обертку и добавить ещё одну функцию-конвертер для преобразования данных во входной коллекции в желаемый результат:
record Result<M, R>(M max, List<R> data){}
public static<T,M,R> Result<M, List<R>> maxResultTwoPass(
Collection<T> data, Comparator<? super T> cmp,
Function<T, R> converter, Function<T, M> maxConverter
) {
return data.stream().max(cmp)
.map(max -> new Result(
maxConverter.apply(max),
data.stream()
.filter(x -> cmp.compare(x, max) == 0)
.map(converter::apply)
.collect(Collectors.toList())
)
).orElse(null);
}
В варианте с кастомным коллектором слегка модифицируется finisher
:
public static<T,M,R> Result<M, List<R>> maxResult(
Collection<T> data, Comparator<? super T> cmp,
Function<T, R> converter, Function<T, M> maxConverter
) {
return data.stream()
.collect(Collector.of(
ArrayList::new,
(List<T> list, T i) -> {
if (list.isEmpty()) {
list.add(i);
} else {
int c = cmp.compare(i, list.get(0));
if (c >= 0) {
if (c > 0) list.clear();
list.add(i);
}
}
},
(lst1, lst2) -> {
if (lst1.isEmpty()) return lst2;
else if (lst2.isEmpty()) return lst1;
int c = cmp.compare(lst1.get(0), lst2.get(0));
if (c == 0) {
lst1.addAll(lst2);
return lst1;
}
return c > 0 ? lst1 : lst2;
},
lst -> lst.isEmpty()
? null
: new Result(
maxConverter.apply(lst.get(0)),
lst.stream().map(converter::apply).collect(Collectors.toList())
)
));
}
В варианте с группировкой модифицируется получение результата (или же можно вернуть Map.Entry
):
public static<T,M extends Comparable,R> Map.Entry<M, List<R>> maxEntryGroup(
Collection<T> data, Function<T, R> converter, Function<T,M> maxConverter
) {
Optional<Map.Entry<M, List<R>>> max = data.stream()
.collect(Collectors.groupingBy(
maxConverter,
Collectors.mapping(converter, Collectors.toList())
)) // Map<M, List<R>>
.entrySet()
.stream()
.max(Map.Entry.comparingByKey());
return max.orElse(null);
}
public static<T,M extends Comparable,R> Result<M, List<R>> maxResultGroup(
Collection<T> data, Function<T, R> converter, Function<T,M> maxConverter
) {
Optional<Map.Entry<M, List<R>>> max = data.stream()
.collect(Collectors.groupingBy(
maxConverter,
Collectors.mapping(converter, Collectors.toList())
)) // Map<M, List<R>>
.entrySet()
.stream()
.max(Map.Entry.comparingByKey());
return max
.map(e -> new Result(e.getKey(), e.getValue()))
.orElse(null);
}
Тесты:
record Person(String name, int age) {}
public static void main(String args[]) {
Map<Person, AtomicInteger> map = Map.of(
new Person("John", 33), new AtomicInteger(1000),
new Person("Joan", 27), new AtomicInteger(4000),
new Person("Jane", 24), new AtomicInteger(7500),
new Person("Jack", 29), new AtomicInteger(7500),
new Person("Jeff", 31), new AtomicInteger(1000),
new Person("Jess", 28), new AtomicInteger(7500)
);
Function<Map.Entry<Person, AtomicInteger>, String> fun = e -> e.getKey().name();
Function<Map.Entry<Person, AtomicInteger>, Integer> res = e -> e.getValue().get();
System.out.println(maxList(
map.entrySet(),
Map.Entry.<Person, AtomicInteger>comparingByValue(Comparator.comparingInt(AtomicInteger::get)),
fun
));
System.out.println(maxResult(
map.entrySet(),
Map.Entry.<Person, AtomicInteger>comparingByValue(Comparator.comparingInt(AtomicInteger::get)),
fun,
res
));
System.out.println(maxEntryGroup(
map.entrySet(),
fun,
res
));
}
[Jess, Jack, Jane]
Result[max=7500, data=[Jess, Jack, Jane]]
7500=[Jess, Jack, Jane]
Для пустой входной коллекции:
[]
null
null
Похожий вопрос на основном SO: