3

I have a list of objects. At first, I need to sort it by type. Than by faceValue. In the end, summarize all quantities:

      class Coin{
            String type;
            BigInteger faceValue;
            BigInteger quantity;
...
       }

            List<Coin> coins = new ArrayList<>();
            coins.add(new Coin("USD", 1, 150));
            coins.add(new Coin("USD", 1, 6));
            coins.add(new Coin("USD", 1, 60));
            coins.add(new Coin("USD", 2, 100));
            coins.add(new Coin("USD", 2, 100));
            coins.add(new Coin("CAD", 1, 111));
            coins.add(new Coin("CAD", 1, 222));

Result list must contains only 3 new coin objects:

Coin("USD", 1 , 216)
Coin("USD", 2 , 200)
Coin("CAD", 1 , 333)

How can this be written only in one lambda expression?

3
  • 2
    When you say 'one lambda expression', do you really mean one lambda expression, or do you mean one call chain which might include multiple lambdas (and method references, etc.)? Commented May 6, 2019 at 14:39
  • Do you need to stick with this design (which isn't optimal at all IMHO), or can you change the class design if you want to? Commented May 6, 2019 at 14:40
  • Yes, it might include multiple lambdas Commented May 6, 2019 at 14:41

1 Answer 1

1

You could solve that using Collectors.toMap as :

public List<Coin> groupedCoins(List<Coin> coins) {
    return new ArrayList<>(
            coins.stream()
                    .collect(Collectors.toMap(
                            coin -> Arrays.asList(coin.getType(), coin.getFaceValue()), Function.identity(),
                            (coin1, coin2) -> {
                                BigInteger netQ = coin1.getQuantity().add(coin2.getQuantity());
                                return new Coin(coin1.getType(), coin1.getFaceValue(), netQ);
                            }))
                    .values());
}

or a further complex one liner grouping and sum as :

public List<Coin> groupedAndSummedCoins(List<Coin> coins) {
    return coins.stream()
            .collect(Collectors.groupingBy(Coin::getType,
                    Collectors.groupingBy(Coin::getFaceValue,
                            Collectors.reducing(BigInteger.ZERO, Coin::getQuantity, BigInteger::add))))
            .entrySet()
            .stream()
            .flatMap(e -> e.getValue().entrySet().stream()
                    .map(a -> new Coin(e.getKey(), a.getKey(), a.getValue())))
            .collect(Collectors.toList());
}
Sign up to request clarification or add additional context in comments.

4 Comments

Why on Earth is the first thing, people think of when grouping by multiple values, using potentially ambiguous, inefficient strings? Instead of coin -> String.format("%s-%s", coin.getType(), coin.getFaceValue()), you can simply use coin -> Arrays.asList(coin.getType(), coin.getFaceValue()).
Tnx! This is what I need!
@Holger I would admit I have seen that being used by a lot of people and read a similar code suggestion from you elsewhere as well, but yet the namespacing using a single key just comes naturally. I understand that these could be ambiguous and would try to avoid this in future use cases. Thanks for the reminding. :)
Besides the possibility of being ambiguous, the concatenation requires the conversion of every object to a string, followed by the string concatenation operation, each of them being potentially expensive, then, the string comparison is not cheap either. In contrast, the List just keeps references to the original objects and implements sufficient hashCode and equals for a sequence of objects. The string approach might be tempting because it looks so simple and hides all the associated costs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.