it-swarm.com.de

Java 8, Lambda: Sortieren in gruppierten Listen und Zusammenführen aller Gruppen zu einer Liste

Basierend auf der folgenden Antwort: https://stackoverflow.com/a/30202075/8760211

Wie kann man jede Gruppe nach stud_id sortieren und dann eine Liste mit allen Schülern als Ergebnis der Gruppierung nach stud_location zurückgeben und dann nach stud_id sortieren?

Es wäre großartig, dies als Erweiterung des vorhandenen Lambda-Ausdrucks zu haben:

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));

Ich brauche die Gruppierung basierend auf der Reihenfolge der Elemente in der Ursprungsliste.

First group: "New York"
Second group: "California"
Third group: "Los Angeles"

1726, "John", "New York"
4321, "Max", "California"
2234, "Andrew", "Los Angeles"
5223, "Michael", "New York"
7765, "Sam", "California"
3442, "Mark", "New York"

Das Ergebnis würde dann wie folgt aussehen:

List<Student> groupedAndSorted = ....

    1726, "John", "New York"
    3442, "Mark", "New York"
    5223, "Michael", "New York"
    4321, "Max", "California"
    7765, "Sam", "California"
    2234, "Andrew", "Los Angeles"

Ich habe folgendes versucht:

studlistGrouped.entrySet().stream().sorted(Comparator.compar‌​ing(Map.Entry::getVa‌​lue))

Das geht aber nicht.

6
ThomasMuller

Wenn ich Sie richtig verstanden habe, möchten Sie einen List<Student> (keine Karte), in dem die Schüler nach ihrem Standort gruppiert und nach IDs innerhalb der Gruppen und sortiert werden, wobei Gruppen ebenfalls nach IDs sortiert werden, nicht nach Ortsnamen. Dies ist möglich, erfordert jedoch eine Gruppierung und zwei Sortierungen:

//first, use your function to group students
Map<String, List<Student>> studlistGrouped = students.stream()
        .collect(Collectors.groupingBy(Student::getLocation, Collectors.toList()));

//then sort groups by minimum id in each of them
List<Student> sorted = studlistGrouped.entrySet().stream()
        .sorted(Comparator.comparing(e -> e.getValue().stream().map(Student::getId).min(Comparator.naturalOrder()).orElse(0)))
        //and also sort each group before collecting them in one list
        .flatMap(e -> e.getValue().stream().sorted(Comparator.comparing(Student::getId))).collect(Collectors.toList());

Dies wird Folgendes erzeugen:

Student{id='1726', name='John', location='New York'}
Student{id='3442', name='Mark', location='New York'}
Student{id='5223', name='Michael', location='New York'}
Student{id='2234', name='Andrew', location='Los Angeles'}
Student{id='4321', name='Max', location='California'}
Student{id='7765', name='Sam', location='California'}

Vielleicht kann dies eleganter gestaltet werden, Vorschläge sind willkommen

EDIT: Zum Zeitpunkt der Erstellung dieser Antwort gab es keine Erwähnung von Gruppierung basierend auf der Reihenfolge der Elemente in der Ursprungsliste in der OPs-Frage. Meine Annahme war also, sowohl Liste als auch Gruppen nach IDs zu sortieren. Für Lösungen, die auf der Reihenfolge in der ursprünglichen Liste basieren, finden Sie weitere Antworten, z. B. die von Holgers

4
Kirill Simonov

Da das Ergebnis eine Liste sein soll, werden Sie nicht gruppiert, sondern einfach sortiert (im Sinne einer Änderung der Reihenfolge gemäß einer definierten Regel). Das Haupthindernis besteht darin, dass die Standorte nach ihrer ersten Begegnung in der ursprünglichen Liste angeordnet werden sollen.

Der einfache Ansatz besteht darin, zuerst diese Ortsreihenfolge festzulegen, gefolgt von einem einzelnen Sortiervorgang:

Map<String,Integer> locationOrder = studlist.stream()
    .collect(HashMap::new,
             (m,s)->m.putIfAbsent(s.stud_location, m.size()),
             (m1,m2)->m2.keySet().forEach(l->m1.putIfAbsent(l, m1.size())));

studlist.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                        .thenComparing(s -> s.stud_id));

Wenn Sie die ursprüngliche Liste nicht ändern können oder wollen, können Sie einfach eine Kopie verwenden:

List<Student> result = new ArrayList<>(studlist);
result.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                      .thenComparing(s -> s.stud_id));

Es ist auch möglich, dies mit einer Gruppierung zu lösen, aber das ist nicht einfacher:

List<Student> result = studlist.stream()
    .collect(Collectors.collectingAndThen(
                Collectors.groupingBy(s -> s.stud_location,
                                      LinkedHashMap::new, Collectors.toList()),
                m -> m.values().stream()
                      .flatMap(l -> l.stream().sorted(Comparator.comparing(s->s.stud_id)))
                      .collect(Collectors.toList())));

Beachten Sie, dass Sie eine LinkedHashMap sammeln müssen, um sicherzustellen, dass die Reihenfolge der Gruppen erhalten bleibt.

4
Holger

Ich war mit demselben Problem konfrontiert und habe alle Lösungen ausprobiert, konnte das Problem jedoch nicht beheben. Dann habe ich versucht, den Weg zu gehen, und schließlich bin ich in der Lage, mein Problem zu beheben.

In obigem Beispiel war meine studlist bereits sortiert, aber ihre Karte wird in jeder Reihenfolge erzeugt, die die Hauptfrage war.

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
**studlistGrouped = new TreeMap<String, List<Student>>(studlistGrouped);**

Mit dieser obigen Lösung wurden alle Schlüssel in Map sortiert. Unser Ziel, nur Ergebnisse in Map zu erhalten, warum versuchen die Leute, dieses in eine Liste umzuwandeln?

1
Nagesh Dalave

Wenn Sie nur gruppieren und sortieren möchten, benötigen Sie keinen groupingBy()-Collector oder gar einen Stream. Verwenden Sie einfach eine zusammengesetzte Sortierung:

studlist.sort(Comparator.comparing(Student::getLocation).thenComparing(Student::getId));
1
shmosel

versuchen Sie zuerst die Sortierung und dann die Gruppierung. Der folgende Code sortiert die Schüler innerhalb des Ortes. 

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation))
Die Ausgabe ist in diesem Fall 

{New York=[1726  John  New York, 3442  Mark  New York, 5223  Michael  New York], Los Angeles=[2234  Andrew  Los Angeles], California=[4321  Max  California, 7765  Sam  California]}

Wenn Sie möchten, dass Standorte auch sortiert werden, verwenden Sie einen Code-Ausschnitt wie unten

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation, TreeMap::new, Collectors.toList()))

Die Ausgabe in diesem Fall ist {California=[4321 Max California, 7765 Sam California], Los Angeles=[2234 Andrew Los Angeles], New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York]}

Student class implementiert Comparable und die Methode compareTo basiert auf der id. 

1
practprogrammer

Sie können eine Zeile hinzufügen:

studlistGrouped.values().forEach(list -> list.sort(Comparator.comparing(Student::getId)));

Oder Sie können Ihren eigenen Sammler schreiben.

Ich weiß, welche ich wählen würde.

1
Bohemian

nicht 100% klar, ob Sie einen Map<String, List<Student>> oder nur einen List<Student> erwarten, trotzdem sind hier beide Lösungen:

importe:

import static Java.util.stream.Collectors.*;
import Java.util.*;
import Java.util.function.Function;

abrufen eines Map<String, List<Student>>, wobei jeder List<Student> Schüler enthält, die nach ihren IDs sortiert sind.

Map<String, List<Student>> resultSet = studlist.stream()
      .collect(groupingBy(Student::getLocation,
             mapping(Function.identity(),
                  collectingAndThen(toList(),
                      e -> e.stream().sorted(Comparator.comparingInt(Student::getId))
                                            .collect(toList())))));

wenn Sie dagegen nur eine Liste von Student-Objekten abrufen möchten, die nach einer bestimmten Eigenschaft sortiert sind, wäre es eine Verschwendung von Ressourcen, eine groupingBy, sorted, collect auszuführen und die Kartenwerte irgendwie in eine einzige Liste zu reduzieren. Stattdessen sortieren Sie einfach die Student-Objekte in der Liste und geben einen Sortierschlüssel an, d. H.

studlist.sort(Comparator.comparingInt(Student::getId));

oder 

studlist.sort(Comparator.comparing(Student::getLocation));

oder abhängig davon, ob Sie nach mehr als einer Eigenschaft sortieren möchten, können Sie etwas wie shmosel answer tun.

1
Aomine

Zuerst über das Sortieren von innerhalb jeder Gruppe. Collectors.groupingBy hat eine zweite Variante, mit der Sie einen Kollektor angeben können, der zum Generieren der Gruppen verwendet wird. Der von Ihnen angegebene Collector kann einer sein, der die Elemente auf sortierte Weise sammelt (z. B. eine TreeSet). Als Abschlussoperation werden sie in eine sortierte Liste umgewandelt. Ein solcher Collector kann mit Collectors.collectingAndThen() erstellt werden.

Zum Beispiel habe ich mit Integers versucht:

List<Integer> list = Arrays.asList(9, 2, 43, 6, 5, 3, 87, 56, 87, 67, 77, 22, 23, 1);
System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector 
            set -> new ArrayList<>(set)))));                // finishing operation

Ausgabe:

{0=[3, 6, 9, 87], 1=[1, 22, 43, 67], 2=[2, 5, 23, 56, 77]}

Ich bin mir sicher, dass Sie das in Ihren Fall übersetzen können. Möglicherweise müssen Sie eine TreeSet mit einem benutzerdefinierten Vergleicher erstellen, damit die Schüler in jeder Gruppe so angeordnet werden, wie Sie möchten, oder wenn Schüler immer auf dieselbe Weise sortiert werden, Student implementieren Comparable.

Zweitens über das Sortieren der Gruppen. Collectors.groupingBy erstellt standardmäßig eine HashMap, für die keine bestimmte Reihenfolge der Schlüssel festgelegt ist (oben werden die Schlüssel zufällig richtig sortiert). Um auch die Schlüssel zu bestellen, müssen Sie die Collectors.groupingBy-Variante verwenden, mit der Sie auch die Ergebniskarte erstellen können, die glücklicherweise auch existiert:

System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        new TreeMap<>((a, b) -> b.compareTo(a)),            // map creator
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector
            set -> new ArrayList<>(set)))));                // finishing operation

Ich habe einen benutzerdefinierten Komparator für die Karte angegeben, um zu zeigen, dass die Reihenfolge tatsächlich unterschiedlich ist. Das Ergebnis ist:

{2=[2, 5, 23, 56, 77], 1=[1, 22, 43, 67], 0=[3, 6, 9, 87]}

Ob dies lesbarer und wartbarer ist als eine gute alte Java-8-Lösung, ist Geschmackssache ...

1
Hoopje

Sie müssen nicht nach Ort gruppieren, da die Eingabe und Ausgabe denselben Datentyp haben. Sie können einfach mehrere Komparatoren miteinander verketten und die Eingabe sortieren.

List<Student> groupedAndSorted = studlist.stream()
       .sorted(Comparator.comparing(Student::getStudLocation)
                .thenComparing(Comparator.comparing(Student::getStudId)))
                .thenComparing(Comparator.comparing(Student::getStudName)))
       .collect(Collectors.toList());

Als Offtopic würde ich sagen, dass Sie Ihre Datenstruktur ändern, um Lombok zu verwenden, um Getter/Setter/Konstruktoren https://projectlombok.org/features/Data automatisch zu generieren. Sie sollten auch eine allgemeinere Namenskonvertierung verwenden, d. H. Das Präfix "stud_" von Attributen entfernen.

import lombok.Data;
@Data
class Student {
    String id;
    String name;
    String location;
}