Статьи

Java 8: группировка по коллекциям

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

Ссылка: Java 8: Группировка с коллекциями нашего партнера JCG Марка Нидхэма в блоге Марка Нидхэма .