2015-11-22 21 views
14

Làm cách nào để giới hạn groupBy theo từng mục nhập?Nhóm giới hạnBởi trong Java 8

Ví dụ (dựa trên ví dụ này: 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")); 

Phương pháp này trở lại nhóm đơn giản:

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

gì nếu tôi muốn giới hạn những bộ sưu tập trở lại? Giả sử tôi chỉ muốn các lớp N đầu tiên cho mỗi giáo viên. Nó được hoàn thiện bằng cách nào?

+3

Ý của bạn là gì trước tiên ?, Bạn có nghĩa là các lớp có số lớp thấp nhất, tên thấp nhất ASCIIBetically hoặc bất kỳ lựa chọn ngẫu nhiên nào của N lớp. Lưu ý: tập hợp các lớp có thể không có thứ tự. –

+0

@PeterLawrey Bạn nói đúng, tôi không đề cập đến thứ tự đó, nhưng nếu chúng ta muốn một giải pháp toàn diện hơn và tổng quát hơn - bạn sẽ thấy hạnh phúc nếu bạn thêm một ví dụ sắp xếp (theo một trong các trường) – yossico

Trả lời

15

Nó sẽ có thể giới thiệu một nhà sưu tập mới giới hạn số phần tử trong danh sách kết quả.

Bộ thu này sẽ giữ lại các phần tử đầu của danh sách (in encounter order). Bộ tích lũy và bộ kết hợp vứt bỏ mọi phần tử khi đạt đến giới hạn trong khi thu thập. Mã combiner là một chút khôn lanh nhưng điều này có lợi thế là không có yếu tố bổ sung được thêm vào chỉ để được vứt đi sau này.

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

Và sau đó sử dụng nó như thế này:

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

Đối với điều này, bạn cần phải .stream() kết quả của Bản đồ của bạn. Bạn có thể thực hiện việc này bằng cách thực hiện:

// 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() 
     )); 

Đây không phải là cách tối ưu để thực hiện. Nhưng nếu bạn .limit() trong danh sách ban đầu thì kết quả nhóm sẽ không chính xác. Đây là cách an toàn nhất để đảm bảo giới hạn.

EDIT:

Như đã trình bày trong các ý kiến ​​này hạn chế việc giáo viên, không phải là lớp học mỗi giáo viên. Trong trường hợp đó bạn có thể làm:

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

Không phải tối ưu, tôi cho rằng anh ta đã làm điều này trong nhóm ban đầu. –

+0

Điều này giới hạn số lượng giáo viên trả lại, không phải số lớp học cho mỗi giáo viên. – siegi

+3

Sử dụng 'Map.replaceAll' sẽ tốt hơn trong bước hậu xử lý hơn là thực hiện một luồng riêng biệt cho mỗi phần tử. Nhưng câu trả lời của @ Tunaki thì tốt hơn. –

3

này sẽ cung cấp cho bạn những kết quả mong muốn, nhưng nó vẫn phân loại tất cả các yếu tố của dòng:

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

Bạn có thể sử dụng collectingAndThen để xác định một hoạt động kết liễu trên danh sách kết quả. Bằng cách này, bạn có thể giới hạn, lọc, sắp xếp, ... danh sách:

int limit = 2; 

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

Điều này vẫn sẽ lọc các giá trị sau khi chúng đã được thêm vào bản đồ, nhưng câu trả lời tốt nhất cho đến nay. –

+2

Ý tưởng về người hoàn thiện là tốt đẹp nhưng không cần chi phí O (n) trong bộ hoàn thiện. Bạn có thể làm một cái gì đó như 'list -> list.size() <= limit?list: list.subList (0, limit)) 'thay vào đó. Nhưng tôi vẫn thích giải pháp của Tunaki hơn, điều này không đòi hỏi phải gắn bó thêm các yếu tố trong danh sách. –

+0

Ai là người như thế này ?? –