Deepak Kumar
Deepak Kumar

Reputation: 863

Efficient way to group by a given list based on a key and collect in same list java 8

I have the below class:

class A{
  String property1;
  String property2;
  Double property3;
  Double property4;
}

So the property1 and property2 is the key.

class Key{
      String property1;
      String property2; 
}

I already have a list of A like below:

List<A> list=new ArrayList<>();

I want to group by using the key and add to another list of A in order to avoid having multiple items with same key in the list:

Function<A, Key> keyFunction= r-> Key.valueOf(r.getProperty1(), r.getProperty2());

But then while doing group by I have to take a sum of property3 and average of property4.

I need an efficient way to do it.

Note: I have skipped the methods of the given classes.

Upvotes: 1

Views: 943

Answers (2)

Hadi
Hadi

Reputation: 17299

Try like this:

 Map<A,List<A>> map = aList
                     .stream()
                     .collect(Collectors
                             .groupingBy(item->new A(item.property1,item.property2)));

List<A> result= map.entrySet().stream()
            .map(list->new A(list.getValue().get(0).property1,list.getValue().get(0).property1)
                    .avgProperty4(list.getValue())
                    .sumProperty3(list.getValue()))
            .collect(Collectors.toList());

and create avgProperty4 and sumProperty3 methods like to this

public A sumProperty3(List<A> a){
  this.property3 = a.stream().mapToDouble(A::getProperty3).sum();
  return this;
}

public A avgProperty4(List<A> a){
   this.property4 =  a.stream().mapToDouble(A::getProperty4).average().getAsDouble();
   return this;
}

result = aList.stream().collect(Collectors
            .groupingBy(item -> new A(item.property1, item.property2),
                    Collectors.collectingAndThen(Collectors.toList(), list ->
                            new A(list.get(0).property1, list.get(0).property1)
                                    .avgProperty4(list).sumProperty3(list))
            )
    );

Upvotes: 0

Eugene
Eugene

Reputation: 120968

Collecting to a Map is unavoidable since you want to group things. A brute-force way to do that would be :

yourListOfA
      .stream()
      .collect(Collectors.groupingBy(
             x -> new Key(x.getProperty1(), x.getProperty2()),
             Collectors.collectingAndThen(Collectors.toList(),
                   list -> {
                        double first = list.stream().mapToDouble(A::getProperty3).sum();
                        // or any other default
                        double second = list.stream().mapToDouble(A::getProperty4).average().orElse(0D);
                        A a = list.get(0);
                        return new A(a.getProperty1(), a.getProperty2(), first, second);
            })))
     .values();

This could be slightly improved for example in the Collectors.collectingAndThen to only iterate the List once, for that a custom collector would be required. Not that complicated to write one...

Upvotes: 2

Related Questions