2014-05-02 21 views
8

Chcę nauczyć się używać składni Java 8 ze strumieniami i utknąłem.Grupowanie Java od jednego do wielu

Łatwo jest zgrupować, gdy masz jeden klucz dla każdej wartości. Ale co jeśli mam listę kluczy dla każdej wartości i nadal chcę je zaklasyfikować za pomocą funkcji groupingBy? Czy muszę rozbić go na kilka stwierdzeń, czy może jest trochę magii strumienia, którą można zrobić, aby była prostsza.

Jest to kod podstawowy:

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

Działa świetnie, jeśli istnieje tylko jeden Wykonawca dla każdego albumu. Ale muszę zwrócić listę, ponieważ album może mieć wielu artystów. Album i Artysta są oczywiście ilustrowane, mam prawdziwe typy.

Prawdopodobnie jest to proste rozwiązanie, ale nie odnalazłem go od jakiegoś czasu, więc wzywam wspólny mózg, który reprezentuje ta strona rozwiązać. :) Kompleksowe rozwiązanie jest również mile widziane w przypadku, gdy prosty nie istnieje.

W klasie albumu lub jako metoda użytkowego biorąc albumu jako argumentu:

Artist getArtist(); // ok 

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

Cheers, Mikael Grev

+0

... Jest prawdopodobnie proste rozwiązanie: 'Various Artists' ;-) – mcalex

Odpowiedz

13

myślę, że jesteś po Collectors.mapping która może być przekazywana jako drugi argument do groupingBy

kompletny przykład

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


} 
+0

Co imponująca prędkość! Wydaje się działać (kompilator jest w porządku, nie można jeszcze go uruchomić z powodu refaktoryzacji). Miałem nadzieję na proste "zawijanie" rzeczy, ale czasami rzeczy, które powinny być proste, nie są ... –

+0

Kopiuję-wklejam to do Eclipse i ja dostaliśmy błąd kompilatora: "Niezgodność typu: nie można przekonwertować z mapy > na mapę >". –

0

Jeśli ktoś szuka przykładowego przykładu, poniższe informacje powinny być przydatne.

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

} 
0

Korzystanie Guava's Multimap można mieć następujący kod:

Zwróć uwagę, że nawet jeśli masz SetMultimap<Artist, Album> wskutek tego jest równoważna pożądanego wyniku Map<Artist, List<Album>>.

myślę, że to jest nieco jaśniejsze;)

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