Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aggregate List of objects in Java

Do we have any aggregator function in Java to perform the below aggregation?

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

List contains data as below.

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

The aggregation criteria is Subject & Dep. The resultant object needs to be

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

This aggregation can be achieved by manually iterating through the list and create an aggregated list. Example as below.

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

But is there any aggregation function or feature of Java 8 which we can leverage?

like image 449
Swadeesh Avatar asked Jun 23 '16 07:06

Swadeesh


People also ask

How do you aggregate a list of objects in Java?

This aggregation can be achieved by manually iterating through the list and create an aggregated list. Example as below. But is there any aggregation function or feature of Java 8 which we can leverage? Streams with a grouping collector might help you out here.

What is aggregate object in Java?

When an object A contains a reference to another object B or we can say Object A has a HAS-A relationship with Object B, then it is termed as Aggregation. Aggregation helps in reusing the code. Object B can have utility methods and which can be utilized by multiple objects.

What are the aggregate operations in Java 8?

Aggregate operations − Stream supports aggregate operations like filter, map, limit, reduce, find, match, and so on. Pipelining − Most of the stream operations return stream itself so that their result can be pipelined.

How to aggregate data from two or more objects in Java?

You can use Stream and Collector which provides groupingBy () method to do this. Since its one of the most common ways to aggregate data, it has a real benefit, coupled that with the various overloaded version of groupingBy () method which also allows you to perform grouping objects concurrently by using concurrent Collectors.

What is Association composition and aggregation in Java?

Association, Composition and Aggregation in Java. Association is relation between two separate classes which establishes through their Objects. Association can be one-to-one, one-to-many, many-to-one, many-to-many. In Object-Oriented programming, an Object communicates to other Object to use functionality and services provided by that object.

What is entity aggregation in Java?

Aggregation in Java If a class have an entity reference, it is known as Aggregation. Aggregation represents HAS-A relationship. Consider a situation, Employee object contains many informations such as id, name, emailId etc.

How can I Group objects in Java 8?

Looking at both techniques, it's clear that Java 8 has made your job a lot easier. To build a group of objects from a list in Java SE 6 or 7, you must iterate over the list, inspect each element, and place them into their own list. To store these groupings, you'll also need a Map.


Video Answer


2 Answers

You can use reduction. Sample to aggregate mark1 is as follows.

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

      }

}

Still looking into generating the output via a single functions. Will update once completed.

like image 173
Jaiprakash Avatar answered Sep 20 '22 17:09

Jaiprakash


Using standards collectors in the JDK, you can do it like this (assuming the creation of a Tuple3<E1, E2, E3> class):

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

This will first group the elements by their subject, then by department and reduces the resulting values in the second map by summing their marks.

Running it on the list of persons you have in your example, you'll get as output:

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

In this case you may also want to use another variant using the toMap collector. The logic remains the same, the function to map the values will create a map containing the department as a key, and the grade of the student as a value. The merge function will be in charge to add or update the mappings.

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

Of course you can question yourself about the "beauty" of these solutions, maybe you want to introduce a custom collector or custom classes to make the intent more clear.

like image 45
Alexis C. Avatar answered Sep 20 '22 17:09

Alexis C.