Группировка Java 8 от одного ко многим

Я хочу научиться использовать синтаксис Java 8 с потоками и немного застрял.

Достаточно легко группировать по, когда у вас есть один ключ для каждого значения. Но что, если у меня есть список ключей для каждого значения, и я все еще хочу классифицировать их с помощью groupingBy? Должен ли я разбить его на несколько операторов или, возможно, есть небольшая магия потока, которая может упростить его.

Это основной код:

List<Album> albums = new ArrayList<>();
Map<Artist, List<Album>> map = albums.stream().collect(Collectors.groupingBy(this::getArtist));

Это прекрасно работает, если для каждого альбома есть только один исполнитель. Но я должен вернуть список, так как в альбоме может быть много исполнителей. Альбом и исполнитель используются для иллюстрации, конечно, у меня есть реальные типы..

Вероятно, есть простое решение, но я давно его не находил, поэтому я призываю коллективный мозг, который представляет этот сайт, решить его. :) Также приветствуется сложное решение, если простого не существует.

В классе Album или как служебный метод, принимающий альбом в качестве аргумента:

Artist getArtist(); // ok

List<Artist> getArtist(); // Not ok, since we now have many "keys" for every Album

С уважением, Микаэль Грев


person Mikael Grev    schedule 02.05.2014    source источник
comment
...Наверное, есть простое решение: "Разные исполнители" ;-)   -  person mcalex    schedule 02.05.2014


Ответы (3)


Я думаю, вам нужен Collectors.mapping, который можно передать в качестве второго аргумента groupingBy.

Полный пример

import java.util.AbstractMap;
import java.util.List;
import java.util.Map;

import static java.util.Arrays.asList;
import static java.util.Map.Entry;
import static java.util.stream.Collectors.*;

public class SO {

    public static void main(String... args) {

        List<Album> albums = asList(
                new Album(
                        asList(
                                new Artist("bob"),
                                new Artist("tom")
                        )
                ),
                new Album(asList(new Artist("bill")))
        );

        Map<Artist, List<Album>> x = albums.stream()
                .flatMap(album -> album.getArtist().stream().map(artist -> pair(artist, album)))
                .collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())));

        x.entrySet().stream().forEach(System.out::println);
    }

    static class Artist {
        private final String name;

        Artist(String name) {
            this.name = name;
        }

        public String toString() {return name;}

    }

    static class Album {
        private List<Artist> artist;

        Album(List<Artist> artist) {
            this.artist = artist;
        }

        List<Artist> getArtist() {
            return artist;
        }

    }

    private static <T,U> AbstractMap.SimpleEntry<T,U> pair(T t, U u) {
        return new AbstractMap.SimpleEntry<T,U>(t,u);
    }


}
person benjiweber    schedule 02.05.2014
comment
Какая впечатляющая скорость! Кажется, это работает (компилятор в порядке, но пока не может работать из-за рефакторинга). Надеялся на простую обертку, но иногда вещи, которые должны быть простыми, не работают. - person Mikael Grev; 02.05.2014
comment
Я скопировал его в Eclipse и получил ошибку компилятора: Несоответствие типов: невозможно преобразовать Map‹Object,List‹Object›› в Map‹SO.Artist,List‹SO.Album››. - person Lukasz Wiktor; 20.08.2014

В случае, если кто-то ищет рабочий пример, ниже должно быть полезно.

import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupSubject {
    public static void main(String[] args) {

        List<ContentItem> items = Arrays.asList(new ContentItem("maths"), new ContentItem("science"),
                new ContentItem("social"), new ContentItem("chemistry"), new ContentItem("maths"));

        Map<String, List<ContentItem>> x = (Map<String, List<ContentItem>>) items.stream()
                .flatMap(item -> item.getSubjects().stream().map(subject -> pair(subject, item)))
                .collect(Collectors.groupingBy(e -> ((SimpleEntry<String, ContentItem>) e).getKey(), Collectors
                        .mapping(e -> ((SimpleEntry<String, ContentItem>) e).getValue(), Collectors.toList())));

        System.out.println(x);

    }

    private static <T, U> AbstractMap.SimpleEntry<T, U> pair(T t, U u) {
        return new AbstractMap.SimpleEntry<T, U>(t, u);
    }

}

class ContentItem {

    private List<String> subjects = new ArrayList<String>();

    public ContentItem(String string) {
        subjects.add(string);
    }

    public List<String> getSubjects() {
        return subjects;
    }

    public void setSubjects(List<String> subjects) {
        this.subjects = subjects;
    }

}
person letmesolve    schedule 17.01.2017

Использование Multimap у вас может быть следующий код:

Обратите внимание, что даже если у вас есть SetMultimap<Artist, Album> в результате, это эквивалентно желаемому результату Map<Artist, List<Album>>.

Я думаю, что это немного яснее ;)

SetMultimap<Artist, Album> artistToAlmbums = HashMultimap.create();
albums.stream().forEach(album -> {
    album.getArtist().forEach(artist -> artistToAlmbums.put(artist, album));
});
person Francois Marot    schedule 24.01.2018