lambda之reduce函数

由于公司的api做了升级,所以最近要迁移自己写的druid查询部分的代码到新项目,然后在迁移的时候有一段用到lambda表达式来求和的例子,时间有些长了一开始看还有些懵,又重新温习了一下,下面写一下整体思路吧(以下是自己编的例子,和业务思想一样):

需求:需要通过druid api根据matchType作为透视维度,查看每个matchType的点击量和薪水,构造的对象如下:

@Data // lombok注解
@Builder // lombok注解
static class Person {
    private double salary;
    private int click;
    private String matchType;
}

执行的伪sql为:SELECT SUM(salary) AS salary, SUM(click) AS click, matchType FROM DRUID_XXXX GROUP BY matchType

通过druid api查询得到处理之后的结果如下:

{
  "result": [
    {
      "salary": 100.1,
      "click": 101,
      "matchType": "pc"
    },
    {
      "salary": 100.2,
      "click": 102,
      "matchType": "mb"
    },
    {
      "salary": 100.3,
      "click": 103,
      "matchType": "ny"
    }
  ]
}

把以上的结果构造成JsonObject如下:

List<JsonObject> result = Arrays.asList( 
        new Gson().toJsonTree(Person.builder().salary(100.1).click(101).matchType("pc").build()).getAsJsonObject(),
        new Gson().toJsonTree(Person.builder().salary(100.2).click(102).matchType("mb").build()).getAsJsonObject(),
        new Gson().toJsonTree(Person.builder().salary(100.3).click(103).matchType("ny").build()).getAsJsonObject()
);

由于druid的查询api对sum的指标有要求,这里提一下为下面写函数做准备:double类型的字段求和计算公式是doubleSum;int或者long类型的字段的求和计算公式是longSum(区别mysql,mysql求和无论double或者long/int都是sum),我们再写一个JavaBean保存一下上面指标的计算公式(其实就是指标的类型信息,long或者double),javaBean如下:

@Data // lombok注解
@Builder // lombok注解
static class Agg {
    private String name;
    private String type;
}
List<Agg> aggregations = Arrays.asList( // 这样查询结果所有指标的类型就存到aggregations这里面了
        Agg.builder().name("salary").type("doubleSum").build(),
        Agg.builder().name("click").type("longSum").build()
);

下面是最重要的部分:通过reduce求每个指标的合计

JsonObject jsonObject = result.stream().reduce(result.get(0),
        (iterate, item) -> {
            // 我们需要把第一个JsonObject的维度信息去掉, 最后的合计不需要维度
            if (iterate == item) {
                requestJson.getDimensions().forEach(item::remove);
                return item;
            }
            aggregations.forEach(aggregation -> {
                String name = aggregation.getName();
                if ("doubleSum".equalsIgnoreCase(aggregation.getType())) {
                    iterate.addProperty(name, iterate.get(name).getAsDouble() + item.get(name).getAsDouble());
                } else {
                    iterate.addProperty(name, iterate.get(name).getAsLong() + item.get(name).getAsLong());
                }
            });
            return iterate;
        }
);

这里解释一下,我们可以先看一下reduce函数的源码:

T reduce(T identity, BinaryOperator<T> accumulator);

T identity:identity的英语翻译是恒等式,也就是计算(函数)的初始值

BinaryOperator<T> accumulator:accumulator的英语翻译是累加器,也就是我们的函数。

可以看一下源码方法官方给的解释:

This is equivalent
* to:
* <pre>{@code
*     T result = identity;
*     for (T element : this stream)
*         result = accumulator.apply(result, element)
*     return result;
* }</pre>
*

这个解释应该很通俗易懂了:reduce方法的第一个参数作为初始值赋给apply方法的第一个参数,然后遍历当前流通过apply(我们自己的业务逻辑)实现求合计或者其他的目的,本章中是求合计

然后再回头看上面自己实现的代码:

我们把reduce方法中的第一个参数设置为result集合的第一个元素,该元素的类型是JsonObject

第二个参数,(iterate, item) 参数列表中的 iterate 第一次代表 reduce的第一个参数,也就是result.get(0),以后每次都代表求和之后的JsonObject。item代表result集合的流,第一次也是从result.get(0)开始,所以第一次开始执行时如果不处理的话会多算一次result.get(0)的JsonObject中的指标,顺便我们可以把result.get(0)的JsonObject中的的维度去掉,最终结果中我们只需要指标的合计,并不需要维度。

(iterate, item) -> {...} 大括号中的实现逻辑看上面“最重要的部分”的代码,这是我们的求和函数: 遍历指标名称,通过指标类型求和后返回累加后的结果,最终返回的结果打印如下:

{"salary":300.6,"click":306}

小小注意事项:

1. reduce函数在第一次执行时如果我们没有处理维度信息,返回的结果打印如下:

 {"salary":300.6,"click":306,"matchType":"pc"}

 会加上result集合第一个元素中的维度信息。

2. reduce函数在第一次执行时,由于初始值和当前流的第一个元素相同,如果不处理(把if (iterate == item)的逻辑去掉),返回的结果打印如下:

 {"salary":400.7,"click":407,"matchType":"pc"}

 可以看到多算了result集合第一个元素中的指标一次

** 附上整体的代码:

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.Builder;
import lombok.Data;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ReduceTest {
    public static void main(String[] args) {
        List<JsonObject> result = Arrays.asList(
                new Gson().toJsonTree(Person.builder().salary(100.1).click(101).matchType("pc").build()).getAsJsonObject(),
                new Gson().toJsonTree(Person.builder().salary(100.2).click(102).matchType("mb").build()).getAsJsonObject(),
                new Gson().toJsonTree(Person.builder().salary(100.3).click(103).matchType("ny").build()).getAsJsonObject()
        );
        Dimensions dimensions = Dimensions.builder().dimensions(Collections.singletonList("matchType")).build();
        List<Agg> aggregations = Arrays.asList(
                Agg.builder().name("salary").type("doubleSum").build(),
                Agg.builder().name("click").type("longSum").build()
        );

        JsonObject jsonObject = result.stream().reduce(result.get(0),
                (iterate, item) -> {
                    // 我们需要把第一个JsonObject的维度信息去掉, 最后的合计不需要维度
                    if (iterate == item) {
                        dimensions.getDimensions().forEach(item::remove);
                        return item;
                    }
                    aggregations.forEach(aggregation -> {
                        String name = aggregation.getName();
                        if ("doubleSum".equalsIgnoreCase(aggregation.getType())) {
                            iterate.addProperty(name, iterate.get(name).getAsDouble() + item.get(name).getAsDouble());
                        } else {
                            iterate.addProperty(name, iterate.get(name).getAsLong() + item.get(name).getAsLong());
                        }
                    });
                    return iterate;
                }
        );
        System.out.println(jsonObject);
    }

    @Data
    @Builder
    static class Person {
        private double salary;
        private int click;
        private String matchType;
    }

    @Data
    @Builder
    static class Dimensions {
        private List<String> dimensions;
    }

    @Data
    @Builder
    static class Agg {
        private String name;
        private String type;
    }
}

===========================割===========================

理解了以后再看没什么,但是要把想法转成文字真的好难。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章