2015-11-22 9 views
14

Wie kann ich groupBy durch jeden Eintrag begrenzen?Limit groupBy in Java 8

Zum Beispiel (in diesem Beispiel basieren: stream groupBy):

studentClasses.add(new StudentClass("Kumar", 101, "Intro to Web")); 
studentClasses.add(new StudentClass("White", 102, "Advanced Java")); 
studentClasses.add(new StudentClass("Kumar", 101, "Intro to Cobol")); 
studentClasses.add(new StudentClass("White", 101, "Intro to Web")); 
studentClasses.add(new StudentClass("White", 102, "Advanced Web")); 
studentClasses.add(new StudentClass("Sargent", 106, "Advanced Web")); 
studentClasses.add(new StudentClass("Sargent", 103, "Advanced Web")); 
studentClasses.add(new StudentClass("Sargent", 104, "Advanced Web")); 
studentClasses.add(new StudentClass("Sargent", 105, "Advanced Web")); 

Diese Methode einfache Gruppe zurück:

Map<String, List<StudentClass>> groupByTeachers = studentClasses 
      .stream().collect(
        Collectors.groupingBy(StudentClass::getTeacher)); 

Was passiert, wenn ich die zurückgegebenen Sammlungen beschränken möchten? Nehmen wir an, ich möchte nur die ersten N Klassen für jeden Lehrer. Wie kann es gemacht werden?

+3

Was meinen Sie zuerst ?, Haben Sie bedeutet, dass die Klassen mit der niedrigsten Klassennummer, den niedrigsten Namen ASCIIBetically oder jede zufällige Auswahl von N Klassen. Hinweis: Die Menge der Klassen kann ungeordnet sein. –

+0

@PeterLawrey Sie haben Recht, ich habe das nicht erwähnt, für mich ist die Reihenfolge irrelevant, aber wenn wir eine gründlichere und allgemeine Lösung wollen - ich bin glücklich, wenn Sie ein Sortierbeispiel hinzufügen (durch eines der Felder) – yossico

Antwort

15

Es wäre möglich, einen neuen Sammler einzuführen, Begrenzt die Anzahl der Elemente in der resultierenden Liste.

Dieser Kollektor behält die Kopfelemente der Liste (in encounter order). Der Akkumulator und der Kombinator werfen alle Elemente weg, wenn die Grenze während der Sammlung erreicht ist. Der Combiner-Code ist ein wenig kompliziert, aber das hat den Vorteil, dass keine zusätzlichen Elemente hinzugefügt werden, die später einfach weggeworfen werden.

private static <T> Collector<T, ?, List<T>> limitingList(int limit) { 
    return Collector.of(
       ArrayList::new, 
       (l, e) -> { if (l.size() < limit) l.add(e); }, 
       (l1, l2) -> { 
        l1.addAll(l2.subList(0, Math.min(l2.size(), Math.max(0, limit - l1.size())))); 
        return l1; 
       } 
      ); 
} 

Und dann ist es wie folgt verwenden:

Map<String, List<StudentClass>> groupByTeachers = 
     studentClasses.stream() 
        .collect(groupingBy(
          StudentClass::getTeacher, 
          limitingList(2) 
        )); 
4

Dazu müssen Sie .stream() das Ergebnis Ihrer Karte. Sie können dies tun, indem Sie:

// Part that comes from your example 
Map<String, List<StudentClass>> groupByTeachers = studentClasses 
      .stream().collect(
        Collectors.groupingBy(StudentClass::getTeacher)); 

// Create a new stream and limit the result 
groupByTeachers = 
    groupByTeachers.entrySet().stream() 
     .limit(N) // The actual limit 
     .collect(Collectors.toMap(
      e -> e.getKey(), 
      e -> e.getValue() 
     )); 

Dies ist kein sehr optimaler Weg, es zu tun. Aber wenn Sie .limit() auf der ersten Liste, dann die Gruppierung Ergebnisse wären falsch. Dies ist der sicherste Weg, um das Limit zu garantieren.

EDIT:

Wie in den Kommentaren erklärt dies den Lehrer begrenzt, nicht die Klasse pro Lehrer. In diesem Fall können Sie tun:

groupByTeachers = 
     groupByTeachers.entrySet().stream() 
      .collect(Collectors.toMap(
       e -> e.getKey(), 
       e -> e.getValue().stream().limit(N).collect(Collectors.toList()) // Limit the classes PER teacher 
      )); 
+0

Nicht sehr optimal, ich nehme an, er meinte das in der ersten Gruppierung zu machen. –

+0

Dies begrenzt die Anzahl der zurückgegebenen Lehrer, nicht die Anzahl der Klassen pro Lehrer. – siegi

+3

Die Verwendung von 'Map.replaceAll' wäre im Nachbearbeitungsschritt besser als ein separater Stream für jedes Element. Aber @ Tunakis Antwort ist sowieso besser. –

3

Dies würde Ihnen das gewünschte Ergebnis, aber es stuft noch alle Elemente des Stroms:

final int N = 10; 
final HashMap<String, List<StudentClass>> groupByTeachers = 
     studentClasses.stream().collect(
      groupingBy(StudentClass::getTeacher, HashMap::new, 
       collectingAndThen(toList(), list -> list.subList(0, Math.min(list.size(), N))))); 
4

Sie collectingAndThen verwenden könnte einen Finisher Betrieb auf der Ergebnisliste zu definieren. Auf diese Weise können begrenzen, filtern, sortieren, ... die Listen:

int limit = 2; 

Map<String, List<StudentClass>> groupByTeachers = 
    studentClasses.stream() 
        .collect(
         groupingBy(
          StudentClass::getTeacher, 
          collectingAndThen(
           toList(), 
           l -> l.stream().limit(limit).collect(toList())))); 
+0

Dies würde immer noch die Werte filtern, nachdem sie bereits zur Karte hinzugefügt wurden, aber die beste Antwort bisher. –

+2

Die Idee eines Finishers ist nett, aber es gibt keine Notwendigkeit für O (N) -Kosten im Finisher. Sie können etwas wie "list -> list.size() <= limit?list: list_list (0, limit)) 'stattdessen. Aber ich bevorzuge immer noch Tunakis Lösung, die es nicht erfordert, die zusätzlichen Elemente in der Liste zu kleben. –

+0

Wer code so? –