У меня есть общая функция, которая принимает Collection<? extends T> ts.
Я также прохожу:
Function<? extends T, ? extends K> classifier который сопоставляет каждый элемент T с ключом K (возможны дубликаты)
Function<? extends T, Integer> evaluator что дает целочисленное значение элемента.
Сама функция имеет вычисление встроенный ("int to int") для каждого произведенного Integer (может быть что-то вроде возведения в квадрат для нашего примера)
Наконец, я хотел бы суммировать все значения для каждого ключа.
Итак, конечный результат: Map<K, Integer>.
Например,
Допустим, у нас есть список ["a","a", "bb"], и мы используем Function.identity для классификации, String::length для оценки и возведения в квадрат как встроенную функцию. Тогда возвращенная карта будет: {"a": 2, "b": 4}
Как я могу это сделать? (Я предполагаю, что предпочтительно использовать Collectors.groupingBy)
К чему привели ваши поиски и исследования? Вы пробовали что-то?
@OleV.V., да, действительно пытался интегрировать его с Collectors.groupingBy. это не сработало
@yaseco Покажи, а не рассказывай.




Вот один из способов сделать это:
public static <T,K> Map<K,Integer> mapper (
Collection<T> ts,
Function<T, K> classifier,
Function<T, Integer> evaluator,
Function<Integer,Integer> calculator)
{
return
ts.stream()
.collect(Collectors.groupingBy(classifier,
Collectors.summingInt(t->evaluator.andThen(calculator).apply(t))));
}
Выход для:
System.out.println (mapper(Arrays.asList("a","a","bb"),Function.identity(),String::length,i->i*i));
является
{bb=4, a=2}
Большое спасибо Эран! (Видимо, я был очень близок к ответу, но вы закрыли для меня пробел - Ура!)
Мне нужно лучше понять часть downstream
@yaseco Collectors.summingInt() позволяет сопоставить каждый из T элементов данной группы с целым числом, а затем суммировать все эти целые числа. t->evaluator.andThen(calculator).apply(t) берет экземпляр T, применяет к нему функцию evaluator, а затем применяет функцию calculator к результату.
t->evaluator.andThen(calculator).apply(t) будет неоднократно вызывать andThen, создавая новый экземпляр Function для каждой оценки этого лямбда-выражения. Прямой способ применить метод к результату другого — это вложенный вызов, например t -> calculator.apply(evaluator.apply(t)). Или, если это действительно должна быть новая функциональная конструкция, evaluator.andThen(calculator)::apply вызовет andThen только один раз и зафиксирует результат.
Или другой подход:
private static <K, T> Map<K, Integer> map(Collection<? extends T> ts,
Function<? super T, ? extends K> classifier,
Function<? super T, Integer> evaluator,
Function<Integer, Integer> andThen) {
return ts.stream()
.collect(Collectors.groupingBy(
classifier,
Collectors.mapping(evaluator.andThen(andThen),
Collectors.reducing(0, Integer::sum))
));
}
И используйте его с:
public static void main(String[] args) {
System.out.println(map(
Arrays.asList("a", "a", "bb"),
Function.identity(),
String::length,
x -> x * x));
}
Забавно, как далеко люди заходят, просто используя ссылки на методы. Здесь с помощью evaluator::apply и andThen::apply можно преобразовать Function в Function. Поскольку это уже экземпляры Function, подойдет простой Collectors.mapping(evaluator, Collectors.mapping(andThen, …)), и как только вы поймете, что ни лямбда-выражение, ни ссылка на метод не требуются, вы можете использовать один комбинированный Collectors.mapping(evaluator.andThen(andThen), …), в отличие от другого ответа, это не будет повторно вызывать andThen…
Потому что мы тоже в квадрате