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;
    }
}

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

理解了以後再看沒什麼,但是要把想法轉成文字真的好難。。

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