Продолжая читать « Функциональное программирование в Java » Венката Субраманиама, я дошел до той части книги, где представлена функция Stream # collect .
Мы хотим собрать коллекцию людей, сгруппировать их по возрасту и вернуть карту (возраст -> имена людей), для которой это пригодится.
Чтобы обновить, вот как выглядит класс Person:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
static class Person { private String name; private int age; Person(String name, int age) { this .name = name; this .age = age; } @Override public String toString() { return String.format( "Person{name='%s', age=%d}" , name, age); } } |
И мы можем написать следующий код в Java 8, чтобы получить карту имен людей, сгруппированных по возрасту:
1
2
3
4
|
Stream<Person> people = Stream.of( new Person( "Paul" , 24 ), new Person( "Mark" , 30 ), new Person( "Will" , 28 )); Map<Integer, List<String>> peopleByAge = people .collect(groupingBy(p -> p.age, mapping((Person p) -> p.name, toList()))); System.out.println(peopleByAge); |
1
|
{ 24 =[Paul], 28 =[Will], 30 =[Mark]} |
Мы запускаем функцию «собирать» над коллекцией, группируя по мере необходимости свойство «возраст» и группируя имена людей, а не самих людей.
Это немного отличается от того, что вы делаете в Ruby, где есть функция group_by, которую вы можете вызывать для коллекции:
1
2
3
|
> people = [ {:name => "Paul" , :age => 24 }, {:name => "Mark" , :age => 30 }, {:name => "Will" , :age => 28 }] > people.group_by { |p| p[:age] } => { 24 =>[{:name=> "Paul" , :age=> 24 }], 30 =>[{:name=> "Mark" , :age=> 30 }], 28 =>[{:name=> "Will" , :age=> 28 }]} |
Это возвращает нам списки людей, сгруппированных по возрасту, но нам нужно применить дополнительную операцию «карта», чтобы вместо этого изменить список имен:
1
2
|
> people.group_by { |p| p[:age] }.map { |k,v| [k, v.map { |person| person[:name] } ] } => [[ 24 , [ "Paul" ]], [ 30 , [ "Mark" ]], [ 28 , [ "Will" ]]] |
На этом этапе у нас есть массив пар (age, names), но, к счастью, в Ruby 2.1.0 есть функция to_h, которую мы можем вызвать, чтобы снова вернуться к хешу:
1
2
|
> people.group_by { |p| p[:age] }.map { |k,v| [k, v.map { |person| person[:name] } ] }.to_h => { 24 =>[ "Paul" ], 30 =>[ "Mark" ], 28 =>[ "Will" ]} |
Если мы хотим следовать Java-подходу к группированию по свойству при выполнении приведения к коллекции, у нас будет что-то вроде следующего:
1
2
|
> people.reduce({}) { |acc, item| acc[item[:age]] ||=[]; acc[item[:age]] << item[:name]; acc } => { 24 =>[ "Paul" ], 30 =>[ "Mark" ], 28 =>[ "Will" ]} |
Если мы используем Clojure, мы можем получить что-то вроде этого:
1
2
3
4
5
|
(def people [{:name "Paul" , :age 24 } {:name "Mark" , :age 30 } {:name "Will" , :age 28 }]) > (reduce (fn [acc [k v]] (assoc-in acc [k] (map :name v))) {} (group-by :age people)) { 28 ( "Will" ), 30 ( "Mark" ), 24 ( "Paul" )} |
Я думал, что версия на Java выглядела немного странно с самого начала, но на самом деле это не так уж и плохо, если разобраться с проблемой в нескольких других языках.
Было бы хорошо узнать, есть ли лучший способ сделать это с помощью Ruby / Clojure!