Java 8: AveragingDouble and mapping to object

Viewed 406

I have list of transaction object as shown below :

List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300, "X", "M1"),
                new Transaction(raoul, 2012, 1000, "T", "M1"),
                new Transaction(raoul, 2011, 400, "S", "M2"),
                new Transaction(mario, 2012, 710, "X", "M1"),
                new Transaction(mario, 2012, 700, "X", "M2"),
                new Transaction(alan, 2012, 950, "T", "M1")
        );

Transaction Class :

class Transaction {
    private final Trader trader;
    private final int year;
    private final int value;
    private String type;
    private String method;
}

Now I need to find the average based on type of transaction per group and finally dump the average into a Result object with some additional data about group whose avg is being computed etc.

prior to computing. following operation are performed on the list :

  • grouping the transaction with year
  • then grouping by method

and the final object produced should have average, year and method's value in it.

Result Class :

class Result {
    private Double avg;
    private int year;
    private String method;
}

Code:

 Map<Integer, Map<String, Result>> res = transactions.stream().collect(
                groupingBy(Transaction::getYear,
                        groupingBy(Transaction::getMethod),
                            collectingAndThen( averagingDouble( t -> "X".equals(t.getType()) ? 1: 0 ),
                                    v -> new Result(v, GROUP_METHOD? , GROUP_YEAR?))
                )
        );

But the GROUP_METHOD & GROUP_YEAR values are not accessible here based on which grouping is done since averagingDouble() produces a double and all other information is lost in this mapping.

is there any way we can grab these fields or to properly map the result into a object ?

2 Answers

You can do this:

After grouping by year and method with mapping collector create Result object as Collectors.mapping(t->new Result(...),), as you know mapping collector takes a collector as a second argument. Because you want to merge them after grouping and calculate the average, collectingAndThen collector is the best choice to perform it here. indeed collectingAndThen after collecting to list takes a function as a finisher(average function) and calculates average value for all elements in the list.

transactions.stream().collect(
      groupingBy(Transaction::getYear,
           groupingBy(Transaction::getMethod,
               mapping(t -> new Result((double) t.getValue(), t.getYear(), t.getMethod()),
                        collectingAndThen(Collectors.toList(), average::apply)))));

and the average function is:

 Function<List<Result>, Result> average = l -> new Result(l.stream()
            .mapToDouble(Result::getAvg)
            .average().orElse(0d), l.get(0).getYear(), l.get(0).getMethod());

You could use averagingDouble to collect to a Map<Integer, Map<String, Double>> as in OP:

Map<Integer, Map<String, Double>> doubleMap = transactions.stream()
                .collect(groupingBy(Transaction::getYear,
                        groupingBy(Transaction::getMethod,
                                averagingDouble(Transaction::getValue))));

Then remap to Map<Integer, Map<String, Result>> :

Map<Integer, Map<String, Result>> resultMap = doubleMap.entrySet().stream()
      .flatMap(my -> my.getValue().entrySet().stream()
             .map(mm -> new Result(mm.getValue(), my.getKey(), mm.getKey())))
      .collect(groupingBy(Result::getYear, toMap(Result::getMethod, Function.identity())));

or in a single statement using collectingAndThen:

 Map<Integer, Map<String, Result>> collect1 = transactions.stream()
     .collect(collectingAndThen(
         groupingBy(Transaction::getYear,
            groupingBy(Transaction::getMethod,averagingDouble(Transaction::getValue))),
         map -> map.entrySet().stream()
                 .flatMap(my -> my.getValue().entrySet().stream()
                    .map(mm -> new Result(mm.getValue(), my.getKey(), mm.getKey())))
                 .collect(groupingBy(Result::getYear, 
                                     toMap(Result::getMethod, Function.identity())))
        ));

output:

{2011={M1=Result[avg=300.0, year=2011, method='M1'], M2=Result[avg=400.0, year=2011, method='M2']},
 2012={M1=Result[avg=886.6666666666666, year=2012, method='M1'], M2=Result[avg=700.0, year=2012, method='M2']}}
Related