Jimq
Jimq

Reputation: 372

Java 8 Lambda - Grouping & Reducing Object

I have a list of Transactions whom I wanted to :

My Code snippets looks like :

Map<Integer, Map<String, Result> res = transactions.stream().collect(Collectors
                            .groupingBy(Transaction::getYear,
                                groupingBy(Transaction::getType),
                                  reducing((a,b)-> new Result("YEAR_TYPE", a.getAmount() + b.getAmount()))
));

Transaction Class :

class Transaction {

    private int year;
    private String type;
    private int value;
}

Result Class :

class Result {

    private String group;
    private int amount;
}

it seems to be not working, what should I do to fix this making sure it works on parallel streams too?

Upvotes: 2

Views: 3711

Answers (2)

Naman
Naman

Reputation: 31868

In the context, Collectors.reducing would help you reduce two Transaction objects into a final object of the same type. In your existing code what you could have done to map to Result type was to use Collectors.mapping and then trying to reduce it.

But reducing without an identity provides and Optional wrapped value for a possible absence. Hence your code would have looked like ;

Map<Integer, Map<String, Optional<Result>>> res = transactions.stream()
        .collect(Collectors.groupingBy(Transaction::getYear,
                Collectors.groupingBy(Transaction::getType,
                        Collectors.mapping(t -> new Result("YEAR_TYPE", t.getValue()),
                                Collectors.reducing((a, b) ->
                                        new Result(a.getGroup(), a.getAmount() + b.getAmount()))))));

to thanks to Holger, one can simplify this further

…and instead of Collectors.mapping(func, Collectors.reducing(op)) you can use Collectors.reducing(id, func, op)


Instead of using this and a combination of Collectors.grouping and Collectors.reducing, transform the logic to use Collectors.toMap as:

Map<Integer, Map<String, Result>> result = transactions.stream()
        .collect(Collectors.groupingBy(Transaction::getYear,
                Collectors.toMap(Transaction::getType,
                        t -> new Result("YEAR_TYPE", t.getValue()),
                        (a, b) -> new Result(a.getGroup(), a.getAmount() + b.getAmount()))));

The answer would stand complete with a follow-up read over Java Streams: Replacing groupingBy and reducing by toMap.

Upvotes: 2

pero_hero
pero_hero

Reputation: 3184

I would use a custom collector:

Collector<Transaction, Result, Result> resultCollector =
 Collector.of(Result::new, // what is the result of this collector

  (a, b) -> { a.setAmount( a.getAmount() + b.getValue());
              a.setGroup("YEAR_TYPE"); }, // how to accumulate a result from a transaction

  (l, r) -> { l.setAmount(l.getAmount() + r.getAmount()); return l; }); // how to combine two 
                                                                        // result instances 
                                                                        // (used in parallel streams)

then you can use the collector to get the map:

Map<Integer, Map<String, Result>> collect = transactions.parallelStream().collect(
       groupingBy(Transaction::getYear,
               groupingBy(Transaction::getType, resultCollector) ) );

Upvotes: 2

Related Questions