2016-08-01 26 views
17

Ich möchte eine Liste von Personen gruppieren. Eine Person hat einige Attribute wie Namen, Land, Stadt, Postleitzahl, etc. Ich den statischen Code geschrieben, die sehr gut funktioniert:Gruppieren von GroupBy Sammlern manuell

Object groupedData = data.stream().collect(groupingBy(Person::getName, Collectors.groupingBy(Person::getCountry, Collectors.groupingBy(Person::getTown)))); 

Aber das Problem ist, das ist es nicht dynamisch. Manchmal möchte ich nur nach Namen und Ort gruppieren, manchmal nach Attributen. Wie kann ich das machen? Non Java 8-Lösungen sind ebenfalls willkommen.

+0

"Manchmal möchte ich nur nach Namen und Stadt gruppieren, manchmal nach Attributen." - abhängig von Benutzereingaben oder was? – Smutje

+1

Siehe auch http://StackOverflow.com/a/34772639/1587046 –

Antwort

11

Sie könnten eine Funktion nimmt eine beliebige Anzahl von Attributen zu einer Gruppe von und konstruieren schaffen die groupingBy - Collector in einer Schleife, jedesmal, wenn die vorherige Version von sich selbst als Kollektor downstream vorbei.

public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) { 
    Iterator<Function<T, ?>> iter = Arrays.asList(groupers).iterator(); 
    Collector collector = Collectors.groupingBy(iter.next()); 
    while (iter.hasNext()) { 
     collector = Collectors.groupingBy(iter.next(), collector); 
    } 
    return (Map) data.stream().collect(collector); 
} 

Beachten Sie, dass die Reihenfolge der grouper Funktionen umgekehrt wird, so dass Sie sie in umgekehrter Reihenfolge durchlaufen müssen (oder sie innerhalb der Funktion umkehren, zum Beispiel Collections.reverse oder Guava der mit Lists.reverse, je nachdem, was Sie bevorzugen).

Object groupedData = collectMany(data, Person::getTown, Person::getCountry, Person::getName); 

Oder so, eine Old-School for Schleife mit dem Array in der Funktion rückgängig zu machen, das heißt, Sie müssen nicht die Barsche in umgekehrter Reihenfolge passieren (aber IMHO ist dies schwieriger zu verstehen):

public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) { 
    Collector collector = Collectors.groupingBy(groupers[groupers.length-1]); 
    for (int i = groupers.length - 2; i >= 0; i--) { 
     collector = Collectors.groupingBy(groupers[i], collector); 
    } 
    return (Map) data.stream().collect(collector); 
} 

Beide Ansätze geben eine Map<?,Map<?,Map<?,T>>> zurück, genau wie in Ihrem ursprünglichen Code. Abhängig davon, was Sie mit diesen Daten machen möchten, könnte es auch sinnvoll sein, eine Map<List<?>,T> zu verwenden, wie von Tunaki vorgeschlagen.

+0

speicherte meinen Tag! Funktioniert nett und reibungslos. Vielen Dank! –

+0

Warum sollten alle Iterator-Boilerplate verwendet werden? Sie können einfach für jeden über das Array – Clashsoft

+1

@Clashsoft Das Problem ist, dass der erste Eintrag anders behandelt werden muss. Meine erste Version war mit 'foreach', aber es brauchte ein Ternär, um den ersten Eintrag zu verarbeiten. –

8

Sie können nach einer Liste gruppieren, die aus den Attributen besteht, die Sie gruppieren möchten.

Stellen Sie sich vor, Sie möchten nach dem Namen und dem Land gruppieren. Dann könnten Sie verwenden:

Map<List<Object>, List<Person>> groupedData = 
    data.stream().collect(groupingBy(p -> Arrays.asList(p.getName(), p.getCountry()))); 

Dies funktioniert, weil zwei Listen gleich betrachtet werden, wenn sie das gleiche Element in der gleichen Reihenfolge enthalten. Daher haben Sie in der resultierenden Karte einen Schlüssel für jedes unterschiedliche Name/Land-Paar und als Wert die Liste der Personen mit diesem spezifischen Namen und Land. Anders gesagt, anstatt "Gruppe nach Name, dann Gruppe nach Land" zu sagen, heißt es effektiv "Gruppe nach Name und Land". Der Vorteil ist, dass Sie nicht mit einer Karte von Karten von Karten etc. enden.

+0

"Der Vorteil ist, dass Sie nicht mit einer Karte von Karten von Karten enden" unter der Annahme, dass das nicht ist, was OP eigentlich _wants_ zu Ende mit ... –

+2

Nun, diese Anforderung war nicht Teil der Frage @tobias_k. Eine Karte mit nur Schlüssel, Wert ist einfacher mit einer Karte mit unbekannten Ebenen der inneren Karten zu arbeiten. – Tunaki

+0

Es war Teil der Frage in dem Sinne, dass es der OP-Code derzeit ist, "der sehr gut funktioniert", so OP. Wie auch immer, ich denke, welcher Ansatz sehr zu befolgen ist, hängt davon ab, wie Sie die Daten verwenden möchten, und es gibt sicherlich Szenarien, in denen Ihr Ansatz einfacher zu handhaben ist. –