2016-06-23 2 views
11

以下の集計を実行するためのJavaの集約関数はありますか?集計Java内のオブジェクトのリスト

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

リストには以下のデータが含まれています。

 
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 

集計基準は、サブジェクト& Depです。得られた目的は、この凝集は、手動でリストを反復することによって達成され、集約されたリストを作成することができる

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

する必要があります。例を次に示します。

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

しかし、私たちが活用できるJava 8の集約関数や機能はありますか?

+2

ここでグループ化コレクターのストリームが役立つかもしれません。 – Kayaman

+1

http://stackoverflow.com/questions/21020562/aggregate-functions-over-a-list-in-javaこれは、Stream.collectを使用して同様のグループ化を行う例を示しています。 – Jaiprakash

+0

手動反復がどのように見える? – shmosel

答えて

3

JDKで標準のコレクターを使用して、あなたがこの(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))))); 

この意志最初のグループ彼らのテーマ別の要素と削減それらのマークを合計することによって第2のマップ内の結果の値を得る。あなたはあなたの例を持っている人物のリストにそれを実行

、あなたが出力として得られます。この場合

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

あなたもtoMapコレクタを使用して別のバリアントを使用することをお勧めします。ロジックは同じままです。値をマップする関数は、部門をキーとして、学生のグレードを値として含むマップを作成します。マージ機能は、マッピングを追加または更新するために担当します。もちろん

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

あなたは多分あなたが意図をより明確にするために、カスタムコレクタまたはカスタムクラスを紹介したい、これらのソリューションの「美」について自分自身を問うことができます。カスタムキークラスでGroup by multiple field names in java 8からのアプローチを使用して

2

reductionを使用できます。マーク1を集めるサンプルは以下の通りです。

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

     } 

} 

出力を1つの関数で生成してみます。一度完了すると更新されます。

1

、私の提案はこれです:

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

DepSubjectequalshashCodeを定義します。このように元のクラスを変更する必要はなく、複数のグループ化基準が必要な場合は、複数のクラスを使用できます。残念ながら、equals、hashCode、getter、settersを持つクラスが必要なので、これはJavaでは非常に冗長です。実際には、クラスが1つの場所でグループ化されている場合は、getterとsetterを省略することもできます。

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 
    } 

リストに結果を収集することもできます。この方法では、カスタムクラスDepSubjectGradesはちょうど中間の操作に使用されています

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

ます。また、関数にgroupingByロジックを抽出できます。

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

この方法では、グループ化できますあなたの人にこれを方法:

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

あなたが唯一の主題によってグループにあなたのオブジェクトをしたい場合は、あなただけ行うことができます:

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

答えをありがとうが、私はこのクラスの構造に固執しています。私はそれらを分割することはできません。また、複数のキーに基づいてグループ化する必要があるため、equalsとhashcodeを定義することはできません。 – Swadeesh

+0

@Swad:あなたは、中間操作のクラスだけが必要です。 'List 'で始め、結果を 'List 'に戻すことができます。複数のグループ化が必要な場合は、異なるクラスを使うこともできます。残念ながら、これはJavaで非常に冗長です... – user140547

+0

@スワッド:いくつかの一般化を追加 – user140547

関連する問題