Estevao Santiago
Estevao Santiago

Reputation: 865

Java Lambda - Trying to sum by 2 groups

I have a object list, which I need to group by 2 different atributes and then sum the values of an attribute, the structure of my object is something like this:

private Long id1;
private Long id2;
private Double amountReserved;
private Double amountRequired;
//... more atributes and getters and setters

So, I then have a list, for example:

List<MyList> list = Arrays.asList(
list(1, A, 50, 200)
list(1, A, 50, 200)
list(1, B, 0, 100)
list(2, A, 10, 15)
list(2, A, 5, 15)
list(3, A, 0, 25));

What I am trying to achieve is a new list with the below structure:

list(1, A, 100, 100) 
list(1, B,   0, 100)
list(2, A,  15,   0)
list(3, A,   0,  25)

Elucidating what is the requisite I am trying to achieve:

  1. Group objects by id1 and id2
  2. sum the amountReservedof the grouped object
  3. subtract amountRequired from the summed amountReserved

What I have so far:

This one got me the groupings as I wanted

Map<Long, Map<String, List<MyList>>> map = null;
map = lista.stream().collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
                    Collectors.groupingBy(PreSeparacaoDto::getCodigoProduto)));

This one sums by group id1, but I am struggling to add the second groupingby on it, as I get syntax errors:

lista.stream()
        .collect(Collectors.groupingBy(PreSeparacaoDto::getCodigoPedido,
                Collectors.summingDouble(PreSeparacaoDto::getProdutoQuantidadeSeparada)))
        .forEach((codigoPedido, ProdutoQuantidadeSeparada) -> System.out.println( codigoPedido + ": " + ProdutoQuantidadeSeparada  ));

My problem is that I failed to get those together ( as per requisite 2) and was not even close to achieve my requisite 3.

I tried to use reduction, as explained here , but honestly, I was not able to replicate it with a single grouping, the reducing is returning an error informing that my parameters don't meet the reducing parameters. I looked for some other options here on stackoverflow and other websites, but without success.

Can someone help me out and poiting where I am failing to combine the reduction with my group, or if that is the correct path I should be following.

Upvotes: 4

Views: 433

Answers (4)

Naman
Naman

Reputation: 31888

You might just be looking for simply Collectors.toMap as :

List<MyList> output = new ArrayList<>(lista.stream()
        .collect(Collectors.toMap(a -> a.getId1() + "-" + a.getId2(), a -> a, (myList1, myList2) -> {
            myList1.amountReserved = myList1.amountReserved + myList2.amountReserved;
            myList1.amountRequired = myList1.amountRequired - myList1.amountReserved;
            return myList1;
        })).values());

Upvotes: 1

Judy1989
Judy1989

Reputation: 437

you can do order by id1 and then order id2 (to make sure elements of the same list and sublist are after each other) and then you do nested foreach (before you iterate the sublist, you init result_reserved_amount to 0 and result_required_amount to the initial value) then you do if same ids (if id1= previous_id1 and id2 = previous_id2) do result_reserved_amount+= current_reserved_amount and result_required_amount -= current_reserved_amount, otherwise update previous_id1, previous_id2, result_reserved_amount, result_required_amount

Upvotes: 0

Thiyagu
Thiyagu

Reputation: 17890

You can stream over the input list twice.

First time, you group by id1, id2 and compute the sum of amount reserved. Second time, you can stream the list again, group it (by id1 and id2) by making use of the above result to find the difference.

Map<Long, Map<Long, Double>> amountReservedGroup = list.stream()
        .collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
                Collectors.summingDouble(MyList::getAmountReserved))));


Map<Long, Map<Long, List<MyList>>> finalResult = list.stream()
        .collect(Collectors.groupingBy(MyList::getId1, Collectors.groupingBy(MyList::getId2,
                Collectors.mapping(o -> new MyList(o.getId1(), o.getId2(),
                                amountReservedGroup.get(o.getId1()).get(o.getId2()),
                                o.getAmountRequired() - amountReservedGroup.get(o.getId1()).get(o.getId2())),
                        Collectors.toList()))));

Note: This does not handle the case when the result of the subtraction is negative!!

As pointed out by nullpointer@ in the comments, will the value of amountRequired be the same for a given id1 and id2?

Upvotes: 1

Highbrainer
Highbrainer

Reputation: 760

I think an easy way is to use Collectors.grouping : you tell it how to group and what to collect.

Here's an example, computing only the sum of AmountReserved :

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class GroupedSums {

    static class MyList {
        Long id1;
        char id2;
        Double amountReserved;
        Double amountRequired;
        public Long getId1() {
            return id1;
        }
        public char getId2() {
            return id2;
        }
        public Double getAmountReserved() {
            return amountReserved;
        }
        public Double getAmountRequired() {
            return amountRequired;
        }
        public MyList(Long id1, char id2, Double amountReserved, Double amountRequired) {
            super();
            this.id1 = id1;
            this.id2 = id2;
            this.amountReserved = amountReserved;
            this.amountRequired = amountRequired;
        }

        Key key() {
            return new Key(id1, id2);
        }

    }

    private static MyList list(Long id1, char id2, Double amountReserved, Double amountRequired) {
        return new MyList(id1, id2, amountReserved, amountRequired);
    }

    public GroupedSums() {      
    }

    private static class Key {

        Long id1;
        char id2;
        public Long getId1() {
            return id1;
        }
        public char getId2() {
            return id2;
        }
        public Key(Long id1, char id2) {
            super();
            this.id1 = id1;
            this.id2 = id2;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id1 == null) ? 0 : id1.hashCode());
            result = prime * result + id2;
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Key other = (Key) obj;
            if (id1 == null) {
                if (other.id1 != null)
                    return false;
            } else if (!id1.equals(other.id1))
                return false;
            if (id2 != other.id2)
                return false;
            return true;
        }
        @Override
        public String toString() {
            return "[id1=" + id1 + ", id2=" + id2 + "]";
        }   

    }

    public static void main(String[] args) {
        List<MyList> list = Arrays.asList(
                list(1L, 'A', 50d, 200d),
                list(1L, 'A', 50d, 200d),
                list(1L, 'B', 0d, 100d),
                list(2L, 'A', 10d, 15d),
                list(2L, 'A', 5d, 15d),
                list(3L, 'A', 0d, 25d));

        list.stream().collect(Collectors.groupingBy(MyList::key, Collectors.summingDouble(MyList::getAmountReserved)))
        .forEach((k,v)->System.out.println("" + k + " :" + v));
    }

}

HTH!

Upvotes: 2

Related Questions