如何对字母数字字符串进行排序

15

我在对包含整数的字符串进行排序时遇到了问题。如果我使用以下代码,我会得到如下排序结果:

1some、2some、20some、21some、3some、some

然而,我希望它的排序方式为:

1some、2some、3some、20some、21some、some

该怎么做呢?

谢谢!

Collections.sort(selectedNodes,
    new Comparator<DefaultMutableTreeNode>() {
    @Override
    public int compare(DefaultMutableTreeNode o1,
        DefaultMutableTreeNode o2) {
        return o1.getUserObject().toString()
            .compareTo(o2.getUserObject().toString());
    }
    });

你应该首先将字符串分为两部分,一部分是整数部分,另一部分是字符串部分。然后首先比较整数部分 - 如果整数部分不相等,则应该出现在前面的字符串是具有较小整数部分的字符串。如果它们相等,则应该出现在前面的字符串是具有字典序更小的字符串部分。 - Olavi Mustanoja
从字符串中解析整数并在比较字符串的其余部分之前进行比较。如果它总是以唯一的整数开头,甚至可以跳过字符串的其余部分。 - Magnilex
字符串可以是任何格式 - 例如:other 1,other 2,1 some 2 other 3,... 因此,我认为将字符串拆分并仅比较整数部分可能会很困难。 - Thaven
一个字符串中可能会出现多个数字吗?“1 some 2”是一个有效的元素吗? - vz0
请注意,您问题中的代码确实按字母数字顺序对对象进行了排序。 - matdev
12个回答

13

这里是一个自包含的示例,演示如何完成此操作(并不特别优化):

final Pattern p = Pattern.compile("^\\d+");
String[] examples = { 
   "1some", "2some", "20some", "21some", "3some", "some", "1abc", "abc"
};
Comparator<String> c = new Comparator<String>() {
    @Override
    public int compare(String object1, String object2) {
        Matcher m = p.matcher(object1);
        Integer number1 = null;
        if (!m.find()) {
            return object1.compareTo(object2);
        }
        else {
            Integer number2 = null;
            number1 = Integer.parseInt(m.group());
            m = p.matcher(object2);
            if (!m.find()) {
                return object1.compareTo(object2);
            }
            else {
                number2 = Integer.parseInt(m.group());
                int comparison = number1.compareTo(number2);
                if (comparison != 0) {
                    return comparison;
                }
                else {
                    return object1.compareTo(object2);
                }
            }
        }
    }
};
List<String> examplesList = new ArrayList<String>(Arrays.asList(examples));
Collections.sort(examplesList, c);
System.out.println(examplesList);

输出

[1abc, 1some, 2some, 3some, 20some, 21some, abc, some]

说明

  • 示例使用常量Pattern来推断数字是否位于String的起始位置。
  • 如果第一个String中不存在该数字,则直接将其与第二个String进行比较。
  • 如果第一个String确实存在该数字,则检查第二个String
  • 如果第二个String中不存在该数字,则再次将两个String按原样进行比较。
  • 如果两个String都包含该数字,则比较整数而不是整个String,因此结果是数字比较而不是字典比较。
  • 如果数字相同,则返回到整个String的字典比较(感谢MihaiC指出这一点)。

@MihaiC 刚刚明白了你的意思。实际上,它会把"abc"放在"some"之前。尽管两者都在末尾,但由于字典比较优先考虑数字而不是字母字符。 - Mena
我知道,我只是在自己想。我认为正确的表示应该是“abc”,然后是“1some”、“2some”等。当然,在任何比较器中,数字都会首先排序,但仍然是一个有趣的问题。 - MihaiC
抱歉回复晚了,但是非常感谢MihaiC - 你的解决方案完美地运行了! - Thaven
2
对我来说,通过执行myList.sort(c)而不是Collections.sort(examplesList, c),它起作用了。但还是谢谢! - Kikadass
2
@Kikadass List.sort 仅适用于 Java 8 及以上版本,而我的答案是针对之前的 Java 版本定制的。如果您正在使用 Java 8,则可以利用 lambda、方法引用等功能来处理此情况。 - Mena
显示剩余10条评论

9

首先制作一个字母数字比较器,将字符串分割成字符串或整数部分。

public class AlphaNumericalComparator implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        List<Object> parts1 = partsOf(o1);
        List<Object> parts2 = partsOf(o2);
        while (!parts1.isEmpty() && !parts2.isEmpty()) {
            Object part1 = parts1.remove(0);
            Object part2 = parts2.remove(0);
            int cmp = 0;
            if (part1 instanceof Integer && part2 instanceof Integer) {
                cmp = Integer.compare((Integer)part1, (Integer)part2);
            } else if (part1 instanceof String && part2 instanceof String) {
                cmp = ((String) part1).compareTo((String) part2);
            } else {
                cmp = part1 instanceof String ? 1 : -1; // XXXa > XXX1
            }
            if (cmp != 0) {
                return cmp;
            }
        }
        if (parts1.isEmpty() && parts2.isEmpty()) {
            return 0;
        }
        return parts1.isEmpty() ? -1 : 1;
    }

    private List<Object> partsOf(String s) {
        List<Object> parts = new LinkedList<>();
        int pos0 = 0;
        int pos = 0;
        boolean wasDigit = false;
        while (true) {
            if (pos >= s.length()
                    || Character.isDigit(s.charAt(pos)) != wasDigit) {
                if (pos > pos0) {
                    String part = s.substring(pos0, pos);
                    parts.add(wasDigit? Integer.valueOf(part) : part);
                    pos0 = pos;
                }
                if (pos >= s.length()) {
                    break;
                }
                wasDigit = !wasDigit;
            }
            ++pos;
        }
        return parts;
    }
};

然后在您自己的代码中使用此比较器。在Java 8中,您可以简单地使用Comparator的静态方法。

感谢您的代码帮助我。 我需要对字母数字字符串进行排序,例如ABC2、ABC3、ABC1。 输出结果如预期:ABC1、ABC2、ABC3。 - Adit choudhary
@Aditchoudhary 是的,而且 ABC9 < ABC10。祝好运。 - Joop Eggen

9

好资源。谢谢。 - Dan Ortega

2

如果您知道模式始终为NUMALPHA或ALPHANUM并且字母始终相同:

最初的回答:

if(str1.length() != str2.length()){
   return str1.length() - str2.length();
}

return str1.compareTo(str2);

1
如果您有包含字母和数字的字符串数组,您可以直接使用排序函数进行排序。
Arrays.sort(Array_name)

然后打印:

for(String a : Array_name)
    System.out.print(a);

1
如何使用比较器(Comparator)在Java中对字符串、字母数字和数字进行排序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AlphaNumericSorting {
    public static void main(String[] args) {
        final Pattern p = Pattern.compile("^\\d+");
        String[] examples = { "CD", "DE", "0A", "0B", "0C", "12", "0K", "TA", "0D", "01", "02", "11", "AB", "MN" };
        Comparator<String> c = new Comparator<String>() {
            @Override
            public int compare(String object1, String object2) {
                Matcher m = p.matcher(object1);
                Integer number1 = null;
                if (!m.find()) {
                    Matcher m1 = p.matcher(object2);
                    if (m1.find()) {
                        return object2.compareTo(object1);
                    } else {
                        return object1.compareTo(object2);
                    }
                } else {
                    Integer number2 = null;
                    number1 = Integer.parseInt(m.group());
                    m = p.matcher(object2);
                    if (!m.find()) {
                        // return object1.compareTo(object2);
                        Matcher m1 = p.matcher(object1);
                        if (m1.find()) {
                            return object2.compareTo(object1);
                        } else {
                            return object1.compareTo(object2);
                        }
                    } else {
                        number2 = Integer.parseInt(m.group());
                        int comparison = number1.compareTo(number2);
                        if (comparison != 0) {
                            return comparison;
                        } else {
                            return object1.compareTo(object2);
                        }
                    }
                }
            }
        };
        List<String> examplesList = new ArrayList<String>(Arrays.asList(examples));
        Collections.sort(examplesList, c);
        System.out.println(examplesList);
    }
}

输出:-

[AB,CD,DE,MN,TA,0A,0B,0C,0D,0K,01,02,11,12]


0

你可以使用正则表达式提取数字部分,用一行代码完成它的核心:

Collections.sort(selectedNodes, new Comparator<DefaultMutableTreeNode>() {
    @Override
    public int compare(DefaultMutableTreeNode o1,
        DefaultMutableTreeNode o2) {
        return Integer.parseInt(o1.getUserObject().toString().replaceAll("\\D", "")) -
            Integer.parseInt(o2.getUserObject().toString().replaceAll("\\D", ""));
    }
});

你的代码中有一个拼写错误。应该是Integer.parseInt(...)此外,这个例子要求字符串中始终有一个数字。有时它可能只有字母,这会引发异常。 - bytor99999

0

这是一个使用Java编写的可行解决方案。如果您对代码有任何建议,请在我的Gist上告诉我。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class FB {

    public static int comparator(String s1, String s2) {

        String[] pt1 = s1.split("((?<=[a-z])(?=[0-9]))|((?<=[0-9])(?=[a-z]))"); 
        String[] pt2 = s2.split("((?<=[a-z])(?=[0-9]))|((?<=[0-9])(?=[a-z]))"); 
//pt1 and pt2 arrays will have the string split in alphabets and numbers

        int i=0;
        if(Arrays.equals(pt1, pt2))
            return 0;
        else{
            for(i=0;i<Math.min(pt1.length, pt2.length);i++)
                if(!pt1[i].equals(pt2[i])) {
                    if(!isNumber(pt1[i],pt2[i])) {
                        if(pt1[i].compareTo(pt2[i])>0)
                            return 1;
                        else
                            return -1;
                    }
                    else {
                        int nu1 = Integer.parseInt(pt1[i]);
                        int nu2 = Integer.parseInt(pt2[i]);
                        if(nu1>nu2)
                            return 1;
                        else
                            return -1;
                    }
                }
        }

        if(pt1.length>i)
            return 1;
        else
            return -1;
    }

    private static Boolean isNumber(String n1, String n2) {
        // TODO Auto-generated method stub
        try {
            int nu1 = Integer.parseInt(n1);
            int nu2 = Integer.parseInt(n2);
            return true;
        }
        catch(Exception x) {
            return false;
        }

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String[] examples = {"1some", "2some", "20some", "21some", "3some", "some", "1abc", "abc"};
        List<String> values = new ArrayList<String>(Arrays.asList(examples));

        System.out.println(values);
        Comparator<String> com = (o1,o2) -> {return comparator(o1,o2);}; //lambda expression

        Collections.sort(values,com);
        System.out.println(values);
    }
}

输出:

[1some, 2some, 20some, 21some, 3some, some, 1abc, abc]
[1abc, 1some, 2some, 3some, 20some, 21some, abc, some]

0
你需要实现自己的比较器来进行这种定制排序。默认的 String.compareTo() 方法似乎会在字符之前对数字进行排序。当 20some 中的 03some 中的 s 进行比较时,0 具有更高的排序优先级,因此整个单词将首先排序。
你需要做的是:尝试将字符串拆分为数字部分和字符部分。由于这些 String 可以由许多这些部分组成(或者它们不是这样的吗?),所以这是一项艰巨的任务。你可以使用像 Alphanum 这样的算法,Murtaza 已经向你展示过了。
如果你想自己实现它,你可以检查数字部分的结束位置。然后使用 Integer.parse() 将其解析为一个 int。如果两个 String 中都存在 int 部分,则比较它们,然后比较剩余部分。这可能不是最专业的解决方案,但作为初学者,你可能希望自己制作这些东西以学习它。

0

你不能使用默认的String compareTo()方法,而是需要按照以下算法比较字符串。

  1. 逐个字符遍历第一个和第二个字符串,并获取所有字符串或数字的块
  2. 检查这些块是数字还是字符串
  3. 如果是数字,则按数字排序,否则使用String compareTo()方法

重复以上步骤。


为什么不能使用默认的String compareTo()方法? - thebiggestlebowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接