2016-06-23 33 views
11

Abbiamo qualche funzione di aggregazione in Java per eseguire l'aggregazione di seguito?Elenco aggregato di oggetti in Java

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

L'elenco contiene i dati seguenti.

 
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 

Il criterio di aggregazione è Oggetto & Dep. L'oggetto risultante deve essere

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

Questa aggregazione può essere ottenuto iterando manualmente attraverso la lista e creare un elenco aggregato. Esempio come sotto.

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

Ma c'è qualche funzione di aggregazione o funzionalità di Java 8 che possiamo sfruttare?

+2

Gli stream con un collettore di raggruppamento potrebbero aiutarti qui. – Kayaman

+1

Cerca in http://stackoverflow.com/questions/21020562/aggregate-functions-over-a-list-in-java ha un esempio di fare un raggruppamento simile usando Stream.collect – Jaiprakash

+0

Puoi dimostrare come sarebbe l'iterazione manuale Guarda? – shmosel

risposta

3

Utilizzando collezionisti standard del JDK, si può fare in questo modo (assumendo che la creazione di una classe 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))))); 

Questo sarà primo gruppo gli elementi per il loro soggetto, poi per reparto e riduce la i valori risultanti nella seconda mappa sommando i loro segni.

esecuzione sulla lista delle persone che hai nel tuo esempio, si otterrà come output:

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

In questo caso si consiglia inoltre di utilizzare un'altra variante utilizzando il toMap collettore. La logica rimane la stessa, la funzione per mappare i valori creerà una mappa contenente il dipartimento come chiave e il voto dello studente come valore. La funzione di unione sarà incaricata di aggiungere o aggiornare i mapping.

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

Naturalmente si può mettere in discussione se stessi circa la "bellezza" di queste soluzioni, forse si vuole introdurre una collezione personalizzata o personalizzati classi per rendere l'intento più chiaro.

2

È possibile utilizzare reduction. Il campione per aggregare mark1 è il seguente.

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

     } 

} 

Ancora in attesa di generare l'output tramite una singola funzione. Aggiornerà una volta completato.

1

Utilizzando l'approccio da Group by multiple field names in java 8 con una classe chiave personalizzata, il mio suggerimento è questo:

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

Il DepSubject definisce equals e hashCode. In questo modo la classe originale non deve essere modificata e, se sono necessari più criteri di raggruppamento, è possibile utilizzare più classi. Sfortunatamente, questo può essere abbastanza dettagliato in Java, dal momento che hai bisogno di una classe con uguali, hashCode, (getter, setter). In realtà, a mio parere, anche i getter e i setter potrebbero essere omessi, se la classe viene utilizzata in un solo posto per il raggruppamento.

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 
    } 

È inoltre possibile raccogliere i risultati in Elenco.In questo modo, le classi personalizzate DepSubject e Grades sono solo utilizzati per le operazioni intermedie:

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

Si potrebbe anche estrarre la logica groupingBy in una funzione:

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

In questo modo, è possibile raggruppare i vostri Persone questo modo:

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

Se si desidera raggruppare l'oggetto per argomento solo, si può solo fare:

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

Grazie per la risposta, ma sono bloccato con questa struttura di classe. Non sarò in grado di dividerli. Inoltre, non sarò in grado di definire equals e hashcode, poiché potrei aver bisogno di raggruppare in base a più chiavi. – Swadeesh

+0

@Swad: servono solo le classi per le operazioni intermedie. puoi iniziare con 'Lista ' e mappare i risultati su un 'Elenco ' Se hai bisogno di più raggruppamenti, puoi anche usare classi diverse. Sfortunatamente, questo è abbastanza dettagliato in Java ... – user140547

+0

@Swad: aggiunto un po 'di generalizzazione – user140547