Home > Net >  Division in Java Streams
Division in Java Streams

Time:01-07

I have stream of objects. I want to group them and calculate the division of sum of values I get from object functions.

import java.util.Map;
import java.util.stream.Stream;

record TestRecord(String type, int a, int b) {
    public int getA() { return a; }
    public int getB() { return b; }
}
    
public class Test {    
    public static void main(String[] args) {
        Stream<TestRecord> testRecords = Stream.of(
            new TestRecord("TYPE1", 1, 2),
            new TestRecord("TYPE1", 3, 4),
            new TestRecord("TYPE2", 5, 6),
            new TestRecord("TYPE2", 7, 8),
            new TestRecord("TYPE2", 9, 10)
        );
    }
    
    //It should return {TYPE1= (4/6), TYPE2 = (21 / 24)}
    //TYPE1= (1 3 / 2 4), TYPE2 = ((5 7 9) / (6 8 10))
    public static Map<String, Double> myFunction(Stream<TestRecord> testRecordStream) {
        //TODO
        return null;
    }
}

I want to return map like {TYPE1= (0.66), TYPE2 = (0.875)}, in above example. I cannot use for loops or forEach function in stream.

CodePudding user response:

1. Using Collectors.teeing

If you are using Java 12 you can use Collectors.teeing to solve this.

We pass two Collectors to Collectors.teeing; the first one sums the return values of function a() and the second for function b(). In the merger function of teeing, we divide the summed values.

public static Map<String, Double> myFunction(Stream<TestRecord> testRecordStream) {
    return testRecordStream
            .collect(Collectors.groupingBy(TestRecord::type,
                    Collectors.teeing(
                            Collectors.summingDouble(TestRecord::a),
                            Collectors.summingDouble(TestRecord::b),
                            (a, b) -> a / b)
            ));
}

2 Using Collectors.reducing Collectors.collectingAndThen

In case, you are using Java version < 12, then you can use reduction mechanism to reduce (here it is sum) the values for a given type. But this would require a simple auxiliary class.

private class Pair {
    private final int l;
    private final int r;
    //Constructors, getters skipped
}

The reducing Collector is used from within a collectingAndThen Collector.

  • The first argument of the reducing collector is the identity value (new Pair(0, 0)).
  • The second argument maps an instance of TestRecord to a Pair.
  • The final argument merges two Pair instances by summing the left and right value (which is l and r here).

Finally, after reducing, we would have a single Pair instance whose l is equal to the sum of all a() function values and r is the sum of all b() function values (for a given type). We divide these two in the finisher of the collectingAndThen Collector.

public static Map<String, Double> myFunction(Stream<TestRecord> testRecordStream) {
    return testRecordStream
            .collect(Collectors.groupingBy(TestRecord::type,
                    Collectors.collectingAndThen(
                            Collectors.reducing(new Pair(0, 0),
                                    testRecord -> new Pair(testRecord.a(), testRecord.b()),
                                    (p1, p2) -> new Pair(p1.l   p2.l, p1.r   p2.r)),
                            pair -> (double) pair.l / pair.r
                    )));
}
  • Related