Java 8 Lambda groupingBy X and Y simultaneously

前端 未结 2 1638
心在旅途
心在旅途 2021-02-03 13:03

I\'m looking for a lambda to refine the data already retrieved. I have a raw resultset, if the user do not change the date I want use java\'s lambda to group by the results for

2条回答
  •  无人共我
    2021-02-03 13:19

    I'm going to be using the Tuple2 type from jOOλ for this exercise, but you can also create your own tuple type if you want to avoid the dependency.

    I'm also assuming you're using this to represent your data:

    class A {
        final int w;
        final int x;
        final int y;
        final int z;
    
        A(int w, int x, int y, int z) {
            this.w = w;
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }
    

    You can now write:

    Map, Tuple2> map =
    Stream.of(
        new A(1, 1, 1, 1),
        new A(1, 2, 3, 1),
        new A(9, 8, 6, 4),
        new A(9, 9, 7, 4),
        new A(2, 3, 4, 5),
        new A(2, 4, 4, 5),
        new A(2, 5, 5, 5))
    .collect(Collectors.groupingBy(
    
        // This is your GROUP BY criteria
        a -> tuple(a.z, a.w),
        Collector.of(
    
            // When collecting, we'll aggregate data into two IntSummaryStatistics
            // for x and y
            () -> tuple(new IntSummaryStatistics(), new IntSummaryStatistics()),
    
            // The accumulator will simply take new t = (x, y) values
            (r, t) -> {
                r.v1.accept(t.x);
                r.v2.accept(t.y);
            },
    
            // The combiner will merge two partial aggregations,
            // in case this is executed in parallel
            (r1, r2) -> {
                r1.v1.combine(r2.v1);
                r1.v2.combine(r2.v2);
    
                return r1;
            }
        )
    ));
    

    Or even better (using the latest jOOλ API):

    Map, Tuple2> map =
    
    // Seq is like a Stream, but sequential only, and with more features
    Seq.of(
        new A(1, 1, 1, 1),
        new A(1, 2, 3, 1),
        new A(9, 8, 6, 4),
        new A(9, 9, 7, 4),
        new A(2, 3, 4, 5),
        new A(2, 4, 4, 5),
        new A(2, 5, 5, 5))
    
    // Seq.groupBy() is just short for Stream.collect(Collectors.groupingBy(...))
    .groupBy(
        a -> tuple(a.z, a.w),
    
        // Because once you have tuples, why not add tuple-collectors?
        Tuple.collectors(
            Collectors.summarizingInt(a -> a.x),
            Collectors.summarizingInt(a -> a.y)
        )
    );
    

    The map structure is now:

    (z, w) -> (all_aggregations_of(x), all_aggregations_of(y))
    

    Calling toString() on the above map will produce:

    {
        (1, 1) = (IntSummaryStatistics{count=2, sum=3, min=1, average=1.500000, max=2}, 
                  IntSummaryStatistics{count=2, sum=4, min=1, average=2.000000, max=3}), 
        (4, 9) = (IntSummaryStatistics{count=2, sum=17, min=8, average=8.500000, max=9}, 
                  IntSummaryStatistics{count=2, sum=13, min=6, average=6.500000, max=7}), 
        (5, 2) = (IntSummaryStatistics{count=3, sum=12, min=3, average=4.000000, max=5}, 
                  IntSummaryStatistics{count=3, sum=13, min=4, average=4.333333, max=5})
    }
    

    You got all your statistics now.

    Side note

    Of course, I don't know your exact requirements, but I suspect you'll be quickly needing more sophisticated aggregations in your report, such as medians, inverse distribution, and all sorts of nice OLAP features, which is when you realise that SQL is just a much easier language for this kind of task.

    On the other hand, we'll definitely add more SQLesque features to jOOλ. This topic has also inspired me to write a full blog post with more details about the described approach.

提交回复
热议问题