中值法排序及LexoRank排序算法简述

概述

在业务中经常会遇到用户拖拽排序的场景,那么拖拽后的结果肯定需要一个值来定义这个排序结果;

一般都是用存数字来代替;但当排序发生变更时,连续的数字不太好维护,比如1,2,3,将3拖拽到1和2之间时,这个sort值应该是多少呢;

如果引入浮点数,那么浮点数的精度会一直膨胀,而后端dbsort字段的精度一般是固定的,并且不能无限制膨胀;

所以在查找了相关资料后,最后采用的是中值法;

定义a,b,c三个sort分别是1000,2000,3000

abc -> acb
c.sort = (a.sort + b.sort)/2

并在后端update这个sort时,比较前后的sort是否<=阈值数值,当<=时,进行重排;

a.sort = 1000,c.sort = 2000,b.sort = 3000

同时增加版本号字段,增加乐观锁;

然后最优的是结合websocket等方案,进行多端同步(如果存在多端操作同一个拖拽泳道的话,比如飞书,腾讯在线文档这种);

我这边因为开发排期等原因,综合比较下来只做了被动刷新(即使用version字段,在update sort时判断是否已经被其他人更新过了)

上述是中值法排序我的理解

LexoRank排序

在熟悉了中值法排序后,我发现了另一个排序方式;LexoRank排序,之前排序的sort是采用的纯数字,而LexoRank使用的字符串(字母+数字,比如77gf);

简单了解下后,发现其本质还是中值法,只是在计算中值时会把字符串转成对应的十进制数;

比如下面1213127ga的转换;

这里不敲文字了,贴下手算的步骤(好久没手算,生疏了-。-)

还有一份demo代码

javascript 复制代码
package com.cloud.product.service.dto;

import java.math.BigInteger;

public class LexoRank {
    private final String alphabet; // e.g., "0123456789abcdefghijklmnopqrstuvwxyz"
    //当前字符集长度
    private final int base;
    private final int length; // 固定秩字符串长度

    public LexoRank(String alphabet, int length) {
        this.alphabet = alphabet;
        this.base = alphabet.length();
        this.length = length;
    }

    // 将秩字符串转换为 BigInteger
    public BigInteger toBigInt(String rank) {
        BigInteger val = BigInteger.ZERO;
        for (int i = 0; i < rank.length(); i++) {
            int idx = alphabet.indexOf(rank.charAt(i));
            if (idx < 0) throw new IllegalArgumentException("Invalid char in rank");
            val = val.multiply(BigInteger.valueOf(base)).add(BigInteger.valueOf(idx));
        }
        return val;
    }

    // 将 BigInteger 转回固定长度的秩字符串(高位补零)
    public String fromBigInt(BigInteger val) {
        BigInteger b = BigInteger.valueOf(base);
        char[] chars = new char[length];
        BigInteger cur = val;
        for (int i = length - 1; i >= 0; i--) {
            BigInteger[] dr = cur.divideAndRemainder(b);
            int idx = dr[1].intValue();
            chars[i] = alphabet.charAt(idx);
            cur = dr[0];
        }
        if (cur.compareTo(BigInteger.ZERO) > 0) {
            throw new IllegalArgumentException("Value too large for length");
        }
        return new String(chars);
    }

    // 生成初始秩(例如中间值)
    public String initialRank() {
        //计算当前sort池下,length下的最大至(即base进制下 base^length值)
        BigInteger max = BigInteger.valueOf(base).pow(length);
        BigInteger mid = max.divide(BigInteger.valueOf(2));
        return fromBigInt(mid);
    }

    // 在 a 和 b 之间生成一个秩(假设 a < b)
    public String between(String a, String b) {
        BigInteger va = toBigInt(a);
        BigInteger vb = toBigInt(b);
        if (va.compareTo(vb) >= 0) throw new IllegalArgumentException("a must be < b");
        BigInteger diff = vb.subtract(va);
        if (diff.compareTo(BigInteger.ONE) <= 0) {
            // 无可用间隙,需要重平衡(调用外部重平衡逻辑)
            throw new IllegalStateException("No gap between ranks; rebalance required");
        }
        BigInteger mid = va.add(vb).divide(BigInteger.valueOf(2));
        return fromBigInt(mid);
    }

    // 生成第一个或最后一个秩(可用于插入到头尾)
    public String beforeFirst() {
        // 取最小可表示值的一半
        BigInteger min = BigInteger.ZERO;
        BigInteger max = BigInteger.valueOf(base).pow(length);
        BigInteger val = max.divide(BigInteger.valueOf(10)); // 例如靠前一点
        return fromBigInt(val);
    }

    public String afterLast() {
        BigInteger max = BigInteger.valueOf(base).pow(length);
        BigInteger val = max.subtract(max.divide(BigInteger.valueOf(10)));
        return fromBigInt(val);
    }

    // 简单示例
    public static void main(String[] args) {
        String alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
        LexoRank lr = new LexoRank(alphabet, 8);

        String r1 = lr.initialRank();
        String r2 = lr.afterLast();

        System.out.println("r1 = " + r1);
        System.out.println("r2 = " + r2);

        // 插入一个在 r1 和 r2 之间的秩
        String mid = lr.between(r1, r2);
        System.out.println("mid = " + mid);

        // 连续插入示例(注意可能触发 rebalance)
        try {
            String m2 = lr.between(r1, mid);
            System.out.println("m2 = " + m2);
        } catch (IllegalStateException ex) {
            System.out.println("需要重平衡: " + ex.getMessage());
        }
    }
}
相关推荐
白鲸开源9 小时前
Apache SeaTunnel Zeta Engine 的 Basic Auth 是怎么工作的?
java·vue.js·github
白鲸开源9 小时前
一文读懂DolphinScheduler插件机制:如何轻松扩展任务类型与数据源
java·架构·github
七牛开发者9 小时前
MCP 到底是什么?为什么 Agent 都想接上它
算法·aigc·agent
北域码匠13 小时前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
用户2986985301414 小时前
Java 实现 Word 文档文本查找与高亮标注
java·后端
宇宙之一粟15 小时前
乐企版式文件生成平台
java·后端·python
plainGeekDev15 小时前
MVC 写法 → MVVM
android·java·kotlin
SL_staff15 小时前
3周搭完MES系统:JVS低代码+JVS-IoT物联网的实战记录
java·前端·低代码
MacroZheng15 小时前
斩获20w star!Claude Code最强插件,AI编程必备!
java·人工智能·后端