中值法排序及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());
        }
    }
}
相关推荐
TongSearch1 小时前
Tongsearch分片的分配、迁移与生命周期管理
java·服务器·数据库·elasticsearch·tongsearch
闻缺陷则喜何志丹1 小时前
【拆位法】P9277 [AGM 2023 资格赛] 反转|普及+
c++·算法·位运算·拆位法
androidstarjack2 小时前
2026 年 IM 即时通讯方案选型实践:4 家主流厂商对比分析
java·spring·spring cloud
2301_815357702 小时前
SpringBoot两大核心数据库连接池:HikariCP与Druid深度实践
java·spring boot
草莓熊Lotso2 小时前
Linux 程序地址空间深度解析:虚拟地址背后的真相
java·linux·运维·服务器·开发语言·c++·人工智能
HAPPY酷2 小时前
std::pair` 与 `std::map` 基础
开发语言·c++·算法
heimeiyingwang2 小时前
官网知识库结构化整理指南
java·sql·架构·database
山东布谷网络科技2 小时前
对标Yalla和Chamet:海外直播语聊APP中多人派对房的关键技术细节
java·开发语言·人工智能·php·语音识别·软件需求·海外电商系统开发
、BeYourself2 小时前
Spring AI 文档切片策略优化指南
java·人工智能·spring