Вопрос

I want to learn how to use the Java 8 syntax with streams and got a bit stuck.

It's easy enough to groupingBy when you have one key for every value. But what if I have a List of keys for every value and still want to categorise them with groupingBy? Do I have to break it into several statements or is there possibly a little stream magic that can be done to make it simpler.

This is the basic code:

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

It works great if there is only one Artist for every Album. But I must return a List since an Album can have many Artists. Album and Artist are used for illustration of course, I have real-world types..

There's probably a simple solution but I haven't found it in a while so I'm calling on the collective brain this site represents to solve it. :) A complex solution is also welcome in case a simple one doesn't exist.

In Album class or as an utility method taking an Album as argument:

Artist getArtist(); // ok

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

Cheers, Mikael Grev

Это было полезно?

Решение

I think you are after Collectors.mapping which can be passed as a second argument to groupingBy

Complete example

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);
    }


}

Другие советы

In case someone looking for a working example, the below should be useful.

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;
    }

}

Using Guava's Multimap you can have following code:

Please note that even if you have SetMultimap<Artist, Album> as a result this is equivalent of the desired result of Map<Artist, List<Album>>.

I think this is a bit clearer ;)

SetMultimap<Artist, Album> artistToAlmbums = HashMultimap.create();
albums.stream().forEach(album -> {
    album.getArtist().forEach(artist -> artistToAlmbums.put(artist, album));
});
public class Test8 {
    static class ContentItem {

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

        public ContentItem(String s) {
            namelist.add(s);
        }

        public List<String> getSubjectList() {
            return namelist;
        }

        public void setSubjectList(List<String> subjectList) {
            this.namelist = subjectList;
        }

        @Override
        public String toString() {
            return " " + namelist;
        }

    }// contentitem

    public static void main(String[] args) {
        List<ContentItem> list = Arrays.asList(new ContentItem("Gini"), new ContentItem("Gina"),
                new ContentItem("Protijayi"), new ContentItem("Gini"), new ContentItem("Gina"));
        System.out.println(list);

        Map<String, List<ContentItem>> map1 = list.stream()
                .flatMap(p -> p.getSubjectList().stream().map(n -> new AbstractMap.SimpleEntry<>(n, p))).collect(
                        Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList())));

        System.out.println(map1);
        //{Gina=[ [Gina],  [Gina]], Gini=[ [Gini],  [Gini]], Protijayi=[ [Protijayi]]}

        // or you can create a pair using AbstractMap.SimpleEntry<>(n,p)
        // Then you can put that pair method
        Map<String, List<ContentItem>> map2 = list.stream().flatMap(p -> p.getSubjectList().stream().map(n -> pair(n,p)))
        .collect(Collectors.groupingBy(Entry::getKey,Collectors.mapping(Entry::getValue, Collectors.toList())));

        System.out.println(map2);
        //{Gina=[ [Gina],  [Gina]], Gini=[ [Gini],  [Gini]], Protijayi=[ [Protijayi]]}

    }// mian

    private static <T, U> AbstractMap.SimpleEntry<T, U> pair(T t, U u) {
        return new AbstractMap.SimpleEntry<T, U>(t, u);
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top