2016-06-23 84 views
11

Chúng ta có bất kỳ hàm tổng hợp nào trong Java để thực hiện kết hợp dưới đây không?Danh sách tổng hợp các đối tượng trong Java

Person { 
    String name; 
    String subject; 
    String department; 
    Long mark1; 
    Long mark2; 
    Long mark3; 
} 

Danh sách chứa dữ liệu như sau.

 
Name |Subject |Department |Mark1 |Mark2 |Mark3 
--------|-----------|-----------|-------|-------|----- 
Clark |English |DEP1  |7  |8  |6 
Michel |English |DEP1  |6  |4  |7 
Dave |Maths  |DEP2  |3  |5  |6 
Mario |Maths  |DEP1  |9  |7  |8 

Tiêu chí tổng hợp là Subject & Dep. Đối tượng kết quả cần phải là

 
Subject  |Department |Mark1 |Mark2 |Mark3 
----------- |-----------|-------|-------|----- 
English  |DEP1  |13  |12  |13 
Maths  |DEP2  |3  |5  |6 
Maths  |DEP1  |9  |7  |8 

Kết hợp này có thể đạt được bằng cách lặp lại theo cách thủ công trong danh sách và tạo danh sách tổng hợp. Ví dụ như dưới đây.

private static List<Person> getGrouped(List<Person> origList) { 
    Map<String, Person> grpMap = new HashMap<String, Person>(); 

    for (Person person : origList) { 
     String key = person.getDepartment() + person.getSubject(); 
     if (grpMap.containsKey(key)) { 
      Person grpdPerson = grpMap.get(key); 
      grpdPerson.setMark1(grpdPerson.getMark1() + person.getMark1()); 
      grpdPerson.setMark2(grpdPerson.getMark2() + person.getMark2()); 
      grpdPerson.setMark3(grpdPerson.getMark3() + person.getMark3()); 
     } else { 
      grpMap.put(key, person); 
     } 
    } 
    return new ArrayList<Person>(grpMap.values()); 
} 

Nhưng có bất kỳ chức năng hoặc tính năng tổng hợp nào của Java 8 mà chúng tôi có thể tận dụng không?

+2

Các luồng có trình thu thập nhóm có thể giúp bạn ở đây. – Kayaman

+1

Nhìn vào http://stackoverflow.com/questions/21020562/aggregate-functions-over-a-list-in-java nó có một ví dụ về thực hiện một nhóm tương tự bằng cách sử dụng Stream.collect – Jaiprakash

+0

Bạn có thể chứng minh cách lặp lại thủ công sẽ nhìn? – shmosel

Trả lời

3

Sử dụng nhà sưu tập tiêu chuẩn trong JDK, bạn có thể làm điều đó như thế này (giả sử việc tạo ra một lớp Tuple3<E1, E2, E3>):

Map<String, Map<String, Tuple3<Long, Long, Long>>> res = 
    persons.stream().collect(groupingBy(p -> p.subject, 
             groupingBy(p -> p.department, 
                reducing(new Tuple3<>(0L, 0L, 0L), 
                  p -> new Tuple3<>(p.mark1, p.mark2, p.mark3), 
                  (t1, t2) -> new Tuple3<>(t1.e1 + t2.e1, t1.e2 + t2.e2, t1.e3 + t2.e3))))); 

sẽ nhóm đầu tiên này các yếu tố theo chủ đề của họ, sau đó bởi bách hóa và làm giảm kết quả các giá trị trong bản đồ thứ hai bằng cách tính tổng số điểm của chúng.

Chạy nó trên danh sách những người bạn có trong ví dụ của bạn, bạn sẽ nhận được như đầu ra:

Maths => DEP2 => (3, 5, 6) 
Maths => DEP1 => (9, 7, 8) 
English => DEP1 => (13, 12, 13) 

Trong trường hợp này bạn cũng có thể muốn sử dụng biến thể khác sử dụng các nhà sưu tập toMap. Logic vẫn giữ nguyên, hàm để ánh xạ các giá trị sẽ tạo một bản đồ chứa bộ phận làm khóa, và điểm của học sinh là một giá trị. Chức năng hợp nhất sẽ chịu trách nhiệm thêm hoặc cập nhật ánh xạ.

Map<String, Map<String, Tuple3<Long, Long, Long>>> res3 = 
     persons.stream() 
       .collect(toMap(p -> p.subject, 
           p -> { 
            Map<String, Tuple3<Long, Long, Long>> value = new HashMap<>(); 
            value.put(p.department, new Tuple3<>(p.mark1, p.mark2, p.mark3)); 
            return value; 
           }, 
           (v1, v2) -> { 
            v2.forEach((k, v) -> v1.merge(k, v, (t1, t2) -> new Tuple3<>(t1.e1 + t2.e1, t1.e2 + t2.e2, t1.e3 + t2.e3))); 
            return v1; 
           } 
       )); 

Tất nhiên bạn có thể đặt câu hỏi cho mình về "vẻ đẹp" của các giải pháp này, có thể bạn muốn giới thiệu một nhà sưu tập tùy chỉnh hoặc tùy chỉnh các lớp học để làm mục đích rõ ràng hơn.

2

Bạn có thể sử dụng reduction. Mẫu để đánh dấu tổng hợp1 như sau.

public class Test { 

    static class Person { 
     Person(String name, String subject, String department, Long mark1, Long mark2, Long mark3) { 
      this.name = name; 
      this.subject = subject; 
      this.department = department; 
      this.mark1 = mark1; 
      this.mark2 = mark2; 
      this.mark3= mark3; 
     } 
      String name; 
      String subject; 
      String department; 
      Long mark1; 
      Long mark2; 
      Long mark3; 

      String group() { 
       return subject+department; 
      } 

      Long getMark1() { 
       return mark1; 
      } 
    } 

     public static void main(String[] args) 
     { 
     List<Person> list = new ArrayList<Test.Person>(); 
     list.add(new Test.Person("Clark","English","DEP1",7l,8l,6l)); 
     list.add(new Test.Person("Michel","English","DEP1",6l,4l,7l)); 
     list.add(new Test.Person("Dave","Maths","DEP2",3l,5l,6l)); 
     list.add(new Test.Person("Mario","Maths","DEP1",9l,7l,8l)); 

     Map<String, Long> groups = list.stream().collect(Collectors.groupingBy(Person::group, Collectors.reducing(
        0l, Person::getMark1, Long::sum))); 

     //Or alternatively as suggested by Holger 
     Map<String, Long> groupsNew = list.stream().collect(Collectors.groupingBy(Person::group, Collectors.summingLong(Person::getMark1))); 

     System.out.println(groups); 

     } 

} 

Vẫn đang tìm cách tạo đầu ra thông qua một hàm duy nhất. Sẽ cập nhật sau khi hoàn thành.

1

Sử dụng cách tiếp cận từ Group by multiple field names in java 8 với một lớp tùy chỉnh quan trọng, đề nghị của tôi là thế này:

Map<DepSubject, Grades> map = persons.stream(). 
      collect(Collectors.groupingBy(x -> new DepSubject(x.department, x.subject), 
      Collectors.reducing(
        new Grades(0, 0, 0), 
        y -> new Grades(y.mark1, y.mark2, y.mark3), 
        (x, y) -> new Grades(x.m1 + y.m1, x.m2 + y.m2, x.m3 + y.m3) 
      ))); 

Các DepSubject định nghĩa equalshashCode. Bằng cách này, lớp gốc không bị thay đổi và nếu cần nhiều tiêu chí nhóm, nhiều lớp có thể được sử dụng. Thật không may, điều này có thể khá chi tiết trong Java, vì bạn cần một lớp với bằng, hashCode, (getters, setters). Trên thực tế, theo ý kiến ​​của tôi, getters và setters cũng có thể được bỏ qua, nếu lớp chỉ được sử dụng ở một nơi để nhóm.

class DepSubject{ 

    String department; 
    String subject; 

    public DepSubject(String department, String subject) { 
     this.department = department; 
     this.subject = subject; 
    } 

    public String getDepartment() { 
     return department; 
    } 
    // equals,hashCode must also be defined for this to work, omitted for brevity 
    } 

Cũng có thể thu thập kết quả vào Danh sách.Bằng cách này, các lớp tùy chỉnh DepSubjectGrades chỉ được sử dụng cho các hoạt động trung gian:

List<Person> list = persons.stream(). 
      collect(Collectors.collectingAndThen(
        Collectors.groupingBy(x -> new DepSubject(x.department, x.subject), 
          Collectors.reducing(
            new Grades(0, 0, 0), 
            y -> new Grades(y.mark1, y.mark2, y.mark3), 
            (x, y) -> new Grades(x.m1 + y.m1, x.m2 + y.m2, x.m3 + y.m3) 
          )), 
        map -> map.entrySet().stream() 
           .map(e -> new Person(null, e.getKey().subject, e.getKey().department, e.getValue().m1, e.getValue().m2, e.getValue().m3)) 
           .collect(Collectors.toList()) 
      )); 

Bạn cũng có thể trích xuất các logic groupingBy vào một chức năng:

private static <T> List<Person> groupBy(List<Person> persons, Function<Person,T> function, BiFunction<T,Grades,Person> biFunction) { 
    return persons.stream(). 
      collect(Collectors.collectingAndThen(
        Collectors.groupingBy(function, 
          Collectors.reducing(
            new Grades(0, 0, 0), 
            y -> new Grades(y.mark1, y.mark2, y.mark3), 
            (x, y) -> new Grades(x.m1 + y.m1, x.m2 + y.m2, x.m3 + y.m3) 
          )), 
        map -> map.entrySet().stream() 
           .map(e -> biFunction.apply(e.getKey(),e.getValue())) 
           .collect(Collectors.toList()) 
      )); 
} 

Bằng cách này, bạn có thể nhóm người bạn này cách:

List<Person> list = groupBy(persons, 
      x -> new DepSubject(x.department, x.subject), 
      (depSubject,grades) -> new Person(null, depSubject.subject, depSubject.department, grades.m1, grades.m2, grades.m3)); 

Nếu bạn chỉ muốn nhóm đối tượng của mình theo chủ đề, bạn chỉ có thể làm:

List<Person> list2 = groupBy(persons, 
      Person::getSubject, 
      (subject,grades) -> new Person(null,subject, null, grades.m1, grades.m2, grades.m3)); 
+0

Cảm ơn câu trả lời, nhưng tôi bị mắc kẹt với cấu trúc Lớp này. Tôi sẽ không thể chia chúng. Ngoài ra, tôi sẽ không thể xác định bằng và hashcode, vì tôi có thể cần nhóm dựa trên nhiều khóa. – Swadeesh

+0

@Swad: bạn chỉ cần các lớp đó cho các hoạt động trung gian. bạn có thể bắt đầu với 'List ' và ánh xạ kết quả trở lại một 'Danh sách ' Nếu bạn cần nhiều nhóm, bạn cũng có thể sử dụng các lớp khác nhau. Thật không may, điều này là khá chi tiết trong Java ... – user140547

+0

@Swad: thêm một số khái quát hóa – user140547

Các vấn đề liên quan