在可能包含数字的字符串上排序
我需要编写一个比较字符串的Java Comparator类,但是要稍作改动。如果要比较的两个字符串在字符串的开头和结尾相同,并且中间不同的部分是整数,则根据这些整数的数值进行比较。例如,我希望以下字符串按显示顺序结束:
- aa
- bbb 3 ccc
- bbb 12 ccc
- 抄送11
- ddd
- eee 3 ddd jpeg2000 eee
- eee 12 ddd jpeg2000 eee
如我们所见,字符串中可能还有其他整数,所以我不能只使用正则表达式来分解任何整数。我正在考虑只是从头开始走弦直到找到不匹配的地方,然后从头开始走直到找到不匹配的地方,然后将中间的部分与正则表达式" [0-9] +",如果比较,则进行数值比较,否则进行词法比较。
有没有更好的办法?
更新我认为我不能保证字符串中的其他数字(可能匹配的数字)周围没有空格,或者不同的数字确实具有空格。
解决方案
将字符串分为字母和数字,因此" foo 12 bar"成为列表(" foo",12," bar"),然后将列表用作排序键。这样,数字将按数字顺序(而不是字母顺序)排序。
在给定的示例中,要比较的数字周围有空格,而其他数字没有空格,那么为什么正则表达式不起作用?
bbb 12 ccc
与
eee 12 ddd jpeg2000 eee
我认为我们必须逐个字符地进行比较。抓住一个字符(如果它是数字字符),请继续抓住它,然后将其重组为单个数字字符串,然后将其转换为" int"。在另一个字符串上重复,然后才进行比较。
Alphanum算法
从网站上
"人们用不同于软件的数字对字符串进行排序。大多数排序算法会比较ASCII值,这会产生与人为逻辑不一致的排序。这是解决方法。"
编辑:这是从该站点到Java比较器实现的链接。
我知道我们在Java中,但是我们可以看一下StrCmpLogicalW的工作方式。这是Explorer在Windows中对文件名进行排序的方式。我们可以在这里查看WINE的实现。
微软的伊恩·格里菲思(Ian Griffiths)有一个实现,他称之为自然排序。移植到Java应该相当容易,无论如何都比从C容易!
更新:eekboom上似乎有一个Java示例可以做到这一点,请参阅" compareNatural"并将其用作比较器进行排序。
如果要编写比较器类,则应实现自己的compare方法,该方法将一个字符一个字符地比较两个字符串。此比较方法应检查我们是要处理字母字符,数字字符还是混合类型(包括空格)。我们必须定义如何使混合类型起作用,数字是在字母字符之前还是之后以及空格在哪里等。
在Linux上,glibc提供了strverscmp(),gnulib也提供了它以实现可移植性。但是,真正的"人类"分类还有许多其他怪癖,例如"甲壳虫"被分类为"甲壳虫,"。对于这个一般性问题,没有简单的解决方案。
简短的回答:根据上下文,我无法确定这是供个人使用的快速代码还是高盛最新内部会计软件的关键部分,因此我首先要说: 。这是一个相当时髦的排序算法;如果可以的话,尝试使用少一些的"扭曲"。
长答案:
在案例中立即想到的两个问题是性能和正确性。非正式地,请确保它是快速的,并确保算法是完全排序的。
(当然,如果我们排序的项目不超过100个,则可以忽略此段。)性能很重要,因为比较器的速度将是排序速度的最大因素(假设排序算法"理想"到典型列表)。在情况下,比较器的速度将主要取决于字符串的大小。字符串似乎很短,因此它们可能不会像列表大小那样占主导地位。
在某些情况下,将每个字符串变成一个字符串-数字-字符串元组,然后按照另一个答案中的建议对这个元组列表进行排序将失败,因为我们显然会出现带有多个数字的字符串。
另一个问题是正确性。具体来说,如果我们描述的算法允许A> B> ...> A,那么排序将是不确定的。就我们而言,尽管我无法证明这一点,但我担心会这样。考虑一些解析情况,例如:
aa 0 aa aa 23aa aa 2a3aa aa 113aa aa 113 aa a 1-2 a a 13 a a 12 a a 2-3 a a 21 a a 2.3 a
有趣的小挑战,我喜欢解决它。
这是我对这个问题的看法:
String[] strs = { "eee 5 ddd jpeg2001 eee", "eee 123 ddd jpeg2000 eee", "ddd", "aaa 5 yy 6", "ccc 555", "bbb 3 ccc", "bbb 9 a", "", "eee 4 ddd jpeg2001 eee", "ccc 11", "bbb 12 ccc", "aaa 5 yy 22", "aaa", "eee 3 ddd jpeg2000 eee", "ccc 5", }; Pattern splitter = Pattern.compile("(\d+|\D+)"); public class InternalNumberComparator implements Comparator { public int compare(Object o1, Object o2) { // I deliberately use the Java 1.4 syntax, // all this can be improved with 1.5's generics String s1 = (String)o1, s2 = (String)o2; // We split each string as runs of number/non-number strings ArrayList sa1 = split(s1); ArrayList sa2 = split(s2); // Nothing or different structure if (sa1.size() == 0 || sa1.size() != sa2.size()) { // Just compare the original strings return s1.compareTo(s2); } int i = 0; String si1 = ""; String si2 = ""; // Compare beginning of string for (; i < sa1.size(); i++) { si1 = (String)sa1.get(i); si2 = (String)sa2.get(i); if (!si1.equals(si2)) break; // Until we find a difference } // No difference found? if (i == sa1.size()) return 0; // Same strings! // Try to convert the different run of characters to number int val1, val2; try { val1 = Integer.parseInt(si1); val2 = Integer.parseInt(si2); } catch (NumberFormatException e) { return s1.compareTo(s2); // Strings differ on a non-number } // Compare remainder of string for (i++; i < sa1.size(); i++) { si1 = (String)sa1.get(i); si2 = (String)sa2.get(i); if (!si1.equals(si2)) { return s1.compareTo(s2); // Strings differ } } // Here, the strings differ only on a number return val1 < val2 ? -1 : 1; } ArrayList split(String s) { ArrayList r = new ArrayList(); Matcher matcher = splitter.matcher(s); while (matcher.find()) { String m = matcher.group(1); r.add(m); } return r; } } Arrays.sort(strs, new InternalNumberComparator());
该算法需要更多的测试,但它的表现似乎还不错。
[编辑]我添加了更多注释以使其更清晰。我看到的答案比开始编写此代码时要多得多。但是,我希望我提供了一个良好的入门基础和/或者一些想法。