Продолжая читать « Функциональное программирование в 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!