首頁  >  文章  >  Java  >  Java怎麼模擬rank/over函數實現取得分組排名

Java怎麼模擬rank/over函數實現取得分組排名

WBOY
WBOY轉載
2023-05-03 13:52:061657瀏覽

背景

#202302##202302三年一班王二小128.00202302三年一班#謝春花136.00202302三年二班馮世傑129.00#202302三年二班
考試批次 班級 名稱 語言
三年一班 張小明 130.00

馬功成

130.00

202302

#三年二班

    魏翩翩
  • 136.00

  • 假設我們有如上數據,現在有一個需求需要統計各學生語文單科成績在班級中的排名和全年段排名,你會如何實現?
  • 很容易的我們想到了 rank() over() 實作

    over()是分析函數,可以和 rank()、 dense_rank() 、 row_number() 一起使用。
使用語法如下:
RANK() OVER(PARTITION BY COLUMN ORDER BY COLUMN)
dense_rank() OVER(PARTITION BY COLUMN ORDER BY COLUMN)
ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN)

解釋:partition by用於給結果集分組,如果沒有指定那麼它將整個結果集作為一個分組。

rank()涵數主要用於排序,並給出序號 ,對於排序並列的資料給予相同序號,並空出並列所佔的名次。

dense_rank() 功能同rank()一樣,差別在於不空出並列所佔的名次

  • row_number()涵數則是依序依序使用,不考慮並列

  • rank 結果為1,2,2,4 dense_rank 結果為1,2,2,3 row_number 結果為1,2 ,3,4

實際應用程式中,會存在資料從其他外部系統存取且資料量不大等多種情況,那麼使用Java程式碼的方式實現分組排名的功能則顯得更加方便。

詳細設計與實作

排序定義類別OrderBy

public class OrderBy {
    private String orderByEL;
    /**
     * 是否升序
     */
    private boolean ascend;

    public OrderBy(){
        //默认升序
        this.ascend = true;
    }

    public String orderByEL(){
        return this.orderByEL;
    }

    public OrderBy orderByEL(String orderByEL){
        this.orderByEL = orderByEL;
        return this;
    }

    public OrderBy ascend(boolean ascend){
        this.ascend = ascend;
        return this;
    }

    public boolean ascend(){
        return this.ascend;
    }
}

該類別定義瞭如下屬性:

排序的fileld

是否升序

  • 取得排名方法

    該方法定義如下:
  • <T> void rankOver(List<T> dataList, String[] partitionByFields, List<OrderBy> orderByList, String resultField, int rankType);
  • 該方法提供了5個入參:

    dataList 排序的資料集
  • partitionByFields 分組field的陣列

    ##orderByList 排序欄位集合

  • resultField 排名結果存放的欄位

rankType 排名方式

#1:不考慮並列(row_number 結果為1,2,3,4)

2 :考慮並列,空出並列所佔的名次(rank 結果為1,2,2,4)

3:考慮並列,不空出並列所佔的名次(dense_rank 1,2,2,3)




此方法具體實作如下

    public static <T> void rankOver(List<T> dataList, String[] partitionByFields, List<OrderBy> orderByList, String resultField, int rankType) {
        if (CollectionUtils.isEmpty(orderByList)) {
            return;
        }
        //STEP_01 剔除掉不参与排名的数据
        List<T> tempList = new ArrayList<>();
        for (T data : dataList) {
            boolean part = true;
            for (OrderBy rptOrderBy : orderByList) {
                Object o1 = executeSpEL(rptOrderBy.orderByEL(), data);
                if (o1 == null) {
                    //参与排序的值为null的话则不参与排名
                    part = false;
                    break;
                }
            }
            if (part) {
                tempList.add(data);
            }
        }
        if (CollectionUtils.isEmpty(tempList)) {
            return;
        }
        //STEP_02 分组
        Map<String, List<T>> groupMap = group(tempList, null, partitionByFields);
        for (List<T> groupDataList : groupMap.values()) {
            order(orderByList, groupDataList);
            if (rankType == 1) {
                int rank = 1;
                for (T temp : groupDataList) {
                    setFieldValue(temp, resultField, rank);
                    rank++;
                }
            } else {
                int prevRank = Integer.MIN_VALUE;
                int size = groupDataList.size();
                for (int i = 0; i < size; i++) {
                    T current = groupDataList.get(i);
                    if (i == 0) {
                        //第一名
                        setFieldValue(current, resultField, 1);
                        prevRank = 1;
                    } else {
                        T prev = groupDataList.get(i - 1);
                        boolean sameRankWithPrev = true;//并列排名
                        for (OrderBy rptOrderBy : orderByList) {
                            Object o1 = executeSpEL(rptOrderBy.orderByEL(), current);
                            Object o2 = executeSpEL(rptOrderBy.orderByEL(), prev);
                            if (!o1.equals(o2)) {
                                sameRankWithPrev = false;
                                break;
                            }
                        }
                        if (sameRankWithPrev) {
                            setFieldValue(current, resultField, getFieldValue(prev, resultField));
                            if (rankType == 2) {
                                ++prevRank;
                            }
                        } else {
                            setFieldValue(current, resultField, ++prevRank);
                        }
                    }
                }
            }
        }
    }

使用案例

定義一個學生類別:

public class Student {
    private String batch;
    private String banji;
    private String name;
    private Double yuwen;
    //extra
    private Integer rank1;
    private Integer rank2;
    
    public Student(String batch, String banji, String name, Double yuwen) {
        this.batch = batch;
        this.banji = banji;
        this.name = name;
        this.yuwen = yuwen;
    }
}
我們寫一個方法,返回以下數據:

public List<Student> getDataList() {
    List<Student> dataList = new ArrayList<>();
    dataList.add(new Student("202302", "三年一班", "张小明", 130.0));
    dataList.add(new Student("202302", "三年一班", "王二小", 128.0));
    dataList.add(new Student("202302", "三年一班", "谢春花", 136.0));
    dataList.add(new Student("202302", "三年二班", "冯世杰", 129.0));
    dataList.add(new Student("202302", "三年二班", "马功成", 130.0));
    dataList.add(new Student("202302", "三年二班", "魏翩翩", 136.0));
    return dataList;
}

取得學生語言成績的班級排名和年段排名,排名採用並列並空出並列所佔用名次的方式。 ###
List<Student> dataList = getDataList();
List<OrderBy> orderByList = new ArrayList<>();
orderByList.add(new OrderBy().orderByEL("yuwen").ascend(false));
//获取全校排名
DataProcessUtil.rankOver(dataList, new String[]{"batch"}, orderByList, "rank1", 2);
//获取班级排名
DataProcessUtil.rankOver(dataList, new String[]{"batch", "banji"}, orderByList, "rank2", 2);
log("语文单科成绩排名情况如下:");

Map<String, List<Student>> groupMap = DataProcessUtil.group(dataList, null, new String[]{"batch"});
for (Map.Entry<String, List<Student>> entry : groupMap.entrySet()) {
    log("考试批次:" + entry.getKey());
    for (Student s : entry.getValue()) {
        log(String.format("班级:%s 学生:%s 语文成绩:%s 班级排名:%s 全校排名:%s", s.getBanji(), s.getName(), s.getYuwen(), s.getRank2(), s.getRank1()));
    }
    log("");
}
###結果如下:#########語言單科成績排名情況如下:###考試批次:202302###班級:三年一班學生:張小明語文成績: 130.0 班級排名:2 全校排名:3###班級:三年一班學生:王二小語文成績:128.0 班級排名:3 全校排名:6###班級:三年一班學生:謝春花語文成績:136.0 班級排名:1 全校排名:1###班級:三年二班學生:馮世傑語文成績:129.0 班級排名:3 全校排名:5###班級:三年二班學生:馬功成語文成績:130.0班級排名:2 全校排名:3###班級:三年二班學生:魏翩翩語成績:136.0 班級排名:1 全校排名:1#########可以看到全校排名中有兩個並列第一名兩個並列第三名,且空出了並列所佔用的名次2 和名次4###

以上是Java怎麼模擬rank/over函數實現取得分組排名的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除