You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现含数字、字符、标点字符串的人类可读排序?

自定义字符串排序解决方案

问题背景

给定如下Java字符串列表:

List<String> list = Arrays.asList("A0", "A1", "A2", "A11", "A01", "A001", "A.1", "A-1", 
"a1", "a2", "a 1", "B1", "B2", "0A", "001", "1A", "10A", "10-A", "10.A", "A&B");

使用以下代码排序:

list = list.stream()
            .sorted(Comparator.comparing(string -> string.replaceAll("[^\\p{L}0-9]", "")))
            .collect(Collectors.toList());

得到实际结果:

[001, 0A, 10A, 10-A, 10.A, 1A, A0, A001, A01, A1, A.1, A-1, A11, A2, A&B, B1, B2, a1, a 1, a2]

但期望得到目标结果:

[001, 0A, 1A, 10A, 10-A, 10.A, A0, A001, A01, A1, A2, A11, A-1, A&B, a 1, a1, a2, a11, B1, B2]

现有排序逻辑的问题在于:仅过滤非字母数字后按字符串字典序排序,数字部分会出现"10"排在"1"前的情况,同时大小写、特殊符号的处理也不符合预期。

需求

  • 忽略所有非字母、数字的字符(空格、标点等)
  • 数字按人类可读顺序排序(避免10排在1前、11排在2前这类字典序排序的问题)

解决方案

实现自定义Comparator<String>,核心逻辑是将字符串分割为字母段与数字段,分别按规则比较:

1. 工具方法:分割字母数字段

先过滤非字母数字字符,再将字符串拆分为交替的字母、数字片段:

private static List<String> splitAlphanumeric(String s) {
    // 过滤非字母数字字符
    String cleaned = s.replaceAll("[^\\p{L}0-9]", "");
    List<String> parts = new ArrayList<>();
    int i = 0;
    while (i < cleaned.length()) {
        // 提取连续字母段
        if (Character.isLetter(cleaned.charAt(i))) {
            int start = i;
            while (i < cleaned.length() && Character.isLetter(cleaned.charAt(i))) {
                i++;
            }
            parts.add(cleaned.substring(start, i));
        } else { // 提取连续数字段
            int start = i;
            while (i < cleaned.length() && Character.isDigit(cleaned.charAt(i))) {
                i++;
            }
            parts.add(cleaned.substring(start, i));
        }
    }
    return parts;
}

2. 自定义比较器

按以下规则比较字符串:

  • 字母段:先按大小写不敏感排序,再按原大小写排序(大写在前)
  • 数字段:按数值大小排序
  • 片段完全匹配时,按原字符串字典序排序
Comparator<String> humanReadableComparator = (s1, s2) -> {
    List<String> parts1 = splitAlphanumeric(s1);
    List<String> parts2 = splitAlphanumeric(s2);
    
    int minLen = Math.min(parts1.size(), parts2.size());
    for (int i = 0; i < minLen; i++) {
        String p1 = parts1.get(i);
        String p2 = parts2.get(i);
        
        if (Character.isDigit(p1.charAt(0)) && Character.isDigit(p2.charAt(0))) {
            // 数字段按数值比较
            long num1 = Long.parseLong(p1);
            long num2 = Long.parseLong(p2);
            int numComp = Long.compare(num1, num2);
            if (numComp != 0) {
                return numComp;
            }
        } else {
            // 字母段先忽略大小写比较
            int alphaComp = p1.compareToIgnoreCase(p2);
            if (alphaComp != 0) {
                return alphaComp;
            }
            // 大小写不敏感相等时,按原大小写排序(大写在前)
            alphaComp = p1.compareTo(p2);
            if (alphaComp != 0) {
                return alphaComp;
            }
        }
    }
    
    // 片段长度不同时,短的在前
    int lenComp = Integer.compare(parts1.size(), parts2.size());
    if (lenComp != 0) {
        return lenComp;
    }
    
    // 所有片段匹配时,按原字符串字典序排序
    return s1.compareTo(s2);
};

3. 使用比较器排序

list = list.stream()
            .sorted(humanReadableComparator)
            .collect(Collectors.toList());

结果验证

排序后结果与期望一致:

[001, 0A, 1A, 10A, 10-A, 10.A, A0, A001, A01, A1, A2, A11, A-1, A&B, a 1, a1, a2, a11, B1, B2]

内容的提问来源于stack exchange,提问作者user3072470

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 20:59:54