我需要编写一个比较字符串的Java Comparator类,但有一个转折点。如果它比较的两个字符串在字符串的开头和结尾是相同的,并且不同的中间部分是整数,则根据这些整数的数值进行比较。例如,我希望以下字符串最终显示:
正如您所看到的,字符串中可能还有其他整数,因此我不能只使用正则表达式来分解任何整数。我正在考虑从一开始就走绳子,直到找到一点不匹配,然后走到最后,直到找到一个不匹配的位,然后比较中间的位到正则表达式“[0-9] +”,如果比较,则进行数值比较,否则进行词法比较。
有更好的方法吗?
更新我认为我不能保证字符串中的其他数字,可能匹配的数字,周围没有空格,或者不同的数字有空格
答案 0 :(得分:96)
来自网站
“人们使用与软件不同的数字对字符串进行排序。大多数排序算法会比较ASCII值,这会产生与人类逻辑不一致的排序。以下是如何修复它。”
编辑:以下是该网站Java Comparator Implementation的链接。
答案 1 :(得分:12)
有趣的小挑战,我很乐意解决它。
以下是我对此问题的看法:
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());
这个算法需要更多的测试,但它似乎表现得相当不错。
[编辑]我添加了一些更清晰的评论。我看到有比我开始编码时更多的答案...但我希望我提供了一个良好的起点和/或一些想法。
答案 2 :(得分:8)
微软的Ian Griffiths有一个他称之为Natural Sorting的C#实现。无论如何,移植到Java应该比C更容易,更容易!
更新: eekboom上似乎有一个Java示例执行此操作,请参阅“compareNatural”并将其用作您的比较器进行排序。
答案 3 :(得分:5)
我在这里提出的实施简单而有效。它不会通过使用正则表达式或方法(如substring(),split(),toCharArray()等)直接或间接分配任何额外的内存。
此实现首先跨越两个字符串,以最大速度搜索不同的第一个字符,而不执行任何特殊处理。仅当这些字符都是数字时才触发特定数字比较。这种实现的副作用是数字被认为比其他字母更大,与默认的字典顺序相反。
public static final int compareNatural (String s1, String s2)
{
// Skip all identical characters
int len1 = s1.length();
int len2 = s2.length();
int i;
char c1, c2;
for (i = 0, c1 = 0, c2 = 0; (i < len1) && (i < len2) && (c1 = s1.charAt(i)) == (c2 = s2.charAt(i)); i++);
// Check end of string
if (c1 == c2)
return(len1 - len2);
// Check digit in first string
if (Character.isDigit(c1))
{
// Check digit only in first string
if (!Character.isDigit(c2))
return(1);
// Scan all integer digits
int x1, x2;
for (x1 = i + 1; (x1 < len1) && Character.isDigit(s1.charAt(x1)); x1++);
for (x2 = i + 1; (x2 < len2) && Character.isDigit(s2.charAt(x2)); x2++);
// Longer integer wins, first digit otherwise
return(x2 == x1 ? c1 - c2 : x1 - x2);
}
// Check digit only in second string
if (Character.isDigit(c2))
return(-1);
// No digits
return(c1 - c2);
}
答案 4 :(得分:5)
我意识到你在java中,但是你可以看看StrCmpLogicalW是如何工作的。这是资源管理器用于在Windows中对文件名进行排序的内容。您可以查看WINE实现here。
答案 5 :(得分:4)
将字符串拆分为字母和数字的运行,因此“foo 12 bar”成为列表(“foo”,12,“bar”),然后使用列表作为排序键。这样,数字将按数字顺序排序,而不是按字母顺序排列。
答案 6 :(得分:3)
我在Java中使用正则表达式提出了一个非常简单的实现:
public static Comparator<String> naturalOrdering() {
final Pattern compile = Pattern.compile("(\\d+)|(\\D+)");
return (s1, s2) -> {
final Matcher matcher1 = compile.matcher(s1);
final Matcher matcher2 = compile.matcher(s2);
while (true) {
final boolean found1 = matcher1.find();
final boolean found2 = matcher2.find();
if (!found1 || !found2) {
return Boolean.compare(found1, found2);
} else if (!matcher1.group().equals(matcher2.group())) {
if (matcher1.group(1) == null || matcher2.group(1) == null) {
return matcher1.group().compareTo(matcher2.group());
} else {
return Integer.valueOf(matcher1.group(1)).compareTo(Integer.valueOf(matcher2.group(1)));
}
}
}
};
}
以下是它的工作原理:
final List<String> strings = Arrays.asList("x15", "xa", "y16", "x2a", "y11", "z", "z5", "x2b", "z");
strings.sort(naturalOrdering());
System.out.println(strings);
[x2a,x2b,x15,xa,y11,y16,z,z,z5]
答案 7 :(得分:2)
这是比Alphanum算法具有以下优点的解决方案:
"0001"
等于"1"
,"01234"
小于"4567"
)public class NumberAwareComparator implements Comparator<String>
{
@Override
public int compare(String s1, String s2)
{
int len1 = s1.length();
int len2 = s2.length();
int i1 = 0;
int i2 = 0;
while (true)
{
// handle the case when one string is longer than another
if (i1 == len1)
return i2 == len2 ? 0 : -1;
if (i2 == len2)
return 1;
char ch1 = s1.charAt(i1);
char ch2 = s2.charAt(i2);
if (Character.isDigit(ch1) && Character.isDigit(ch2))
{
// skip leading zeros
while (i1 < len1 && s1.charAt(i1) == '0')
i1++;
while (i2 < len2 && s2.charAt(i2) == '0')
i2++;
// find the ends of the numbers
int end1 = i1;
int end2 = i2;
while (end1 < len1 && Character.isDigit(s1.charAt(end1)))
end1++;
while (end2 < len2 && Character.isDigit(s2.charAt(end2)))
end2++;
int diglen1 = end1 - i1;
int diglen2 = end2 - i2;
// if the lengths are different, then the longer number is bigger
if (diglen1 != diglen2)
return diglen1 - diglen2;
// compare numbers digit by digit
while (i1 < end1)
{
if (s1.charAt(i1) != s2.charAt(i2))
return s1.charAt(i1) - s2.charAt(i2);
i1++;
i2++;
}
}
else
{
// plain characters comparison
if (ch1 != ch2)
return ch1 - ch2;
i1++;
i2++;
}
}
}
}
答案 8 :(得分:2)
Alphanum algrothim很不错,但它与我正在进行的项目的要求不符。我需要能够正确排序负数和小数。这是我提出的实施。任何反馈都会非常感激。
public class StringAsNumberComparator implements Comparator<String> {
public static final Pattern NUMBER_PATTERN = Pattern.compile("(\\-?\\d+\\.\\d+)|(\\-?\\.\\d+)|(\\-?\\d+)");
/**
* Splits strings into parts sorting each instance of a number as a number if there is
* a matching number in the other String.
*
* For example A1B, A2B, A11B, A11B1, A11B2, A11B11 will be sorted in that order instead
* of alphabetically which will sort A1B and A11B together.
*/
public int compare(String str1, String str2) {
if(str1 == str2) return 0;
else if(str1 == null) return 1;
else if(str2 == null) return -1;
List<String> split1 = split(str1);
List<String> split2 = split(str2);
int diff = 0;
for(int i = 0; diff == 0 && i < split1.size() && i < split2.size(); i++) {
String token1 = split1.get(i);
String token2 = split2.get(i);
if((NUMBER_PATTERN.matcher(token1).matches() && NUMBER_PATTERN.matcher(token2).matches()) {
diff = (int) Math.signum(Double.parseDouble(token1) - Double.parseDouble(token2));
} else {
diff = token1.compareToIgnoreCase(token2);
}
}
if(diff != 0) {
return diff;
} else {
return split1.size() - split2.size();
}
}
/**
* Splits a string into strings and number tokens.
*/
private List<String> split(String s) {
List<String> list = new ArrayList<String>();
try (Scanner scanner = new Scanner(s)) {
int index = 0;
String num = null;
while ((num = scanner.findInLine(NUMBER_PATTERN)) != null) {
int indexOfNumber = s.indexOf(num, index);
if (indexOfNumber > index) {
list.add(s.substring(index, indexOfNumber));
}
list.add(num);
index = indexOfNumber + num.length();
}
if (index < s.length()) {
list.add(s.substring(index));
}
}
return list;
}
}
PS。我想使用java.lang.String.split()方法并使用“lookahead / lookbehind”来保存令牌,但我无法使用我正在使用的正则表达式。
答案 9 :(得分:1)
我创建了一个project来比较不同的实现。它远未完成,但这是一个起点。
答案 10 :(得分:1)
添加到answer制作的@stanislav上。 使用提供的答案时,我遇到了一些问题:
这两个问题已在新代码中修复。我做了一些函数,而不是一些重复的代码集。 differentCaseCompared变量跟踪两个字符串是否相同(除了大小写不同)。如果是这样,则返回减去的前不同大小写字符的值。这样做是为了避免两个字符串因大小写不同而返回0的问题。
public class NaturalSortingComparator implements Comparator<String> {
@Override
public int compare(String string1, String string2) {
int lengthOfString1 = string1.length();
int lengthOfString2 = string2.length();
int iteratorOfString1 = 0;
int iteratorOfString2 = 0;
int differentCaseCompared = 0;
while (true) {
if (iteratorOfString1 == lengthOfString1) {
if (iteratorOfString2 == lengthOfString2) {
if (lengthOfString1 == lengthOfString2) {
// If both strings are the same except for the different cases, the differentCaseCompared will be returned
return differentCaseCompared;
}
//If the characters are the same at the point, returns the difference between length of the strings
else {
return lengthOfString1 - lengthOfString2;
}
}
//If String2 is bigger than String1
else
return -1;
}
//Check if String1 is bigger than string2
if (iteratorOfString2 == lengthOfString2) {
return 1;
}
char ch1 = string1.charAt(iteratorOfString1);
char ch2 = string2.charAt(iteratorOfString2);
if (Character.isDigit(ch1) && Character.isDigit(ch2)) {
// skip leading zeros
iteratorOfString1 = skipLeadingZeroes(string1, lengthOfString1, iteratorOfString1);
iteratorOfString2 = skipLeadingZeroes(string2, lengthOfString2, iteratorOfString2);
// find the ends of the numbers
int endPositionOfNumbersInString1 = findEndPositionOfNumber(string1, lengthOfString1, iteratorOfString1);
int endPositionOfNumbersInString2 = findEndPositionOfNumber(string2, lengthOfString2, iteratorOfString2);
int lengthOfDigitsInString1 = endPositionOfNumbersInString1 - iteratorOfString1;
int lengthOfDigitsInString2 = endPositionOfNumbersInString2 - iteratorOfString2;
// if the lengths are different, then the longer number is bigger
if (lengthOfDigitsInString1 != lengthOfDigitsInString2)
return lengthOfDigitsInString1 - lengthOfDigitsInString2;
// compare numbers digit by digit
while (iteratorOfString1 < endPositionOfNumbersInString1) {
if (string1.charAt(iteratorOfString1) != string2.charAt(iteratorOfString2))
return string1.charAt(iteratorOfString1) - string2.charAt(iteratorOfString2);
iteratorOfString1++;
iteratorOfString2++;
}
} else {
// plain characters comparison
if (ch1 != ch2) {
if (!ignoreCharacterCaseEquals(ch1, ch2))
return Character.toLowerCase(ch1) - Character.toLowerCase(ch2);
// Set a differentCaseCompared if the characters being compared are different case.
// Should be done only once, hence the check with 0
if (differentCaseCompared == 0) {
differentCaseCompared = ch1 - ch2;
}
}
iteratorOfString1++;
iteratorOfString2++;
}
}
}
private boolean ignoreCharacterCaseEquals(char character1, char character2) {
return Character.toLowerCase(character1) == Character.toLowerCase(character2);
}
private int findEndPositionOfNumber(String string, int lengthOfString, int end) {
while (end < lengthOfString && Character.isDigit(string.charAt(end)))
end++;
return end;
}
private int skipLeadingZeroes(String string, int lengthOfString, int iteratorOfString) {
while (iteratorOfString < lengthOfString && string.charAt(iteratorOfString) == '0')
iteratorOfString++;
return iteratorOfString;
}
}
以下是我使用的单元测试。
public class NaturalSortingComparatorTest {
private int NUMBER_OF_TEST_CASES = 100000;
@Test
public void compare() {
NaturalSortingComparator naturalSortingComparator = new NaturalSortingComparator();
List<String> expectedStringList = getCorrectStringList();
List<String> testListOfStrings = createTestListOfStrings();
runTestCases(expectedStringList, testListOfStrings, NUMBER_OF_TEST_CASES, naturalSortingComparator);
}
private void runTestCases(List<String> expectedStringList, List<String> testListOfStrings,
int numberOfTestCases, Comparator<String> comparator) {
for (int testCase = 0; testCase < numberOfTestCases; testCase++) {
Collections.shuffle(testListOfStrings);
testListOfStrings.sort(comparator);
Assert.assertEquals(expectedStringList, testListOfStrings);
}
}
private List<String> getCorrectStringList() {
return Arrays.asList(
"1", "01", "001", "2", "02", "10", "10", "010",
"20", "100", "_1", "_01", "_2", "_200", "A 02",
"A01", "a2", "A20", "t1A", "t1a", "t1AB", "t1Ab",
"t1aB", "t1ab", "T010T01", "T0010T01");
}
private List<String> createTestListOfStrings() {
return Arrays.asList(
"10", "20", "A20", "2", "t1ab", "01", "T010T01", "t1aB",
"_2", "001", "_200", "1", "A 02", "t1Ab", "a2", "_1", "t1A", "_01",
"100", "02", "T0010T01", "t1AB", "10", "A01", "010", "t1a");
}
}
建议!我不确定添加功能是否会改变除可读性之外的其他内容。
P.S:很抱歉为这个问题添加另一个答案。但是我没有足够的代表来评论我为自己使用而修改的答案。
答案 11 :(得分:1)
有趣的问题,这里是我提出的解决方案:
import java.util.Collections;
import java.util.Vector;
public class CompareToken implements Comparable<CompareToken>
{
int valN;
String valS;
String repr;
public String toString() {
return repr;
}
public CompareToken(String s) {
int l = 0;
char data[] = new char[s.length()];
repr = s;
valN = 0;
for (char c : s.toCharArray()) {
if(Character.isDigit(c))
valN = valN * 10 + (c - '0');
else
data[l++] = c;
}
valS = new String(data, 0, l);
}
public int compareTo(CompareToken b) {
int r = valS.compareTo(b.valS);
if (r != 0)
return r;
return valN - b.valN;
}
public static void main(String [] args) {
String [] strings = {
"aaa",
"bbb3ccc",
"bbb12ccc",
"ccc 11",
"ddd",
"eee3dddjpeg2000eee",
"eee12dddjpeg2000eee"
};
Vector<CompareToken> data = new Vector<CompareToken>();
for(String s : strings)
data.add(new CompareToken(s));
Collections.shuffle(data);
Collections.sort(data);
for (CompareToken c : data)
System.out.println ("" + c);
}
}
答案 12 :(得分:1)
在发现这个帖子之前,我在javascript中实现了一个类似的解决方案。尽管语法不同,也许我的策略会很好地找到你。与上面类似,我解析被比较的两个字符串,并将它们分成数组,将字符串分成连续数字。
...
var regex = /(\d+)/g,
str1Components = str1.split(regex),
str2Components = str2.split(regex),
...
I.e。,&#39; hello22goodbye 33&#39; =&GT; [&#39;你好&#39;,22,&#39;再见&#39;,33];因此,您可以浏览数组&#39;在string1和string2之间成对的元素,做一些类型强制(例如,这个元素真的是一个数字?),并在你走路时进行比较。
这里的工作示例:http://jsfiddle.net/F46s6/3/
注意,我目前只支持整数类型,但处理十进制值不会太难修改。
答案 13 :(得分:1)
我的2美分。对我来说效果很好。我主要将它用于文件名。
private final boolean isDigit(char ch)
{
return ch >= 48 && ch <= 57;
}
private int compareNumericalString(String s1,String s2){
int s1Counter=0;
int s2Counter=0;
while(true){
if(s1Counter>=s1.length()){
break;
}
if(s2Counter>=s2.length()){
break;
}
char currentChar1=s1.charAt(s1Counter++);
char currentChar2=s2.charAt(s2Counter++);
if(isDigit(currentChar1) &&isDigit(currentChar2)){
String digitString1=""+currentChar1;
String digitString2=""+currentChar2;
while(true){
if(s1Counter>=s1.length()){
break;
}
if(s2Counter>=s2.length()){
break;
}
if(isDigit(s1.charAt(s1Counter))){
digitString1+=s1.charAt(s1Counter);
s1Counter++;
}
if(isDigit(s2.charAt(s2Counter))){
digitString2+=s2.charAt(s2Counter);
s2Counter++;
}
if((!isDigit(s1.charAt(s1Counter))) && (!isDigit(s2.charAt(s2Counter)))){
currentChar1=s1.charAt(s1Counter);
currentChar2=s2.charAt(s2Counter);
break;
}
}
if(!digitString1.equals(digitString2)){
return Integer.parseInt(digitString1)-Integer.parseInt(digitString2);
}
}
if(currentChar1!=currentChar2){
return currentChar1-currentChar2;
}
}
return s1.compareTo(s2);
}
答案 14 :(得分:0)
我建议不要使用费时的方法,而应使用具有ICU4J library中内置的数字排序功能的,符合区域设置的Unicode字符串比较器。
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class CollatorExample {
public static void main(String[] args) {
// Make sure to choose correct locale: in Turkish uppercase of "i" is "İ", not "I"
RuleBasedCollator collator = (RuleBasedCollator) Collator.getInstance(Locale.US);
collator.setNumericCollation(true); // Place "10" after "2"
collator.setStrength(Collator.PRIMARY); // Case-insensitive
List<String> strings = Arrays.asList("10", "20", "A20", "2", "t1ab", "01", "T010T01", "t1aB",
"_2", "001", "_200", "1", "A 02", "t1Ab", "a2", "_1", "t1A", "_01",
"100", "02", "T0010T01", "t1AB", "10", "A01", "010", "t1a"
);
strings.sort(collator);
System.out.println(String.join(", ", strings));
// Output: _1, _01, _2, _200, 01, 001, 1,
// 2, 02, 10, 10, 010, 20, 100, A 02, A01,
// a2, A20, t1A, t1a, t1ab, t1aB, t1Ab, t1AB,
// T010T01, T0010T01
}
}
答案 15 :(得分:0)
我有一个类似的问题,我的弦内部有空格分隔的段。我以这种方式解决了这个问题:
public class StringWithNumberComparator implements Comparator<MyClass> {
@Override
public int compare(MyClass o1, MyClass o2) {
if (o1.getStringToCompare().equals(o2.getStringToCompare())) {
return 0;
}
String[] first = o1.getStringToCompare().split(" ");
String[] second = o2.getStringToCompare().split(" ");
if (first.length == second.length) {
for (int i = 0; i < first.length; i++) {
int segmentCompare = StringUtils.compare(first[i], second[i]);
if (StringUtils.isNumeric(first[i]) && StringUtils.isNumeric(second[i])) {
segmentCompare = NumberUtils.compare(Integer.valueOf(first[i]), Integer.valueOf(second[i]));
if (0 != segmentCompare) {
// return only if uneven numbers in case there are more segments to be checked
return segmentCompare;
}
}
if (0 != segmentCompare) {
return segmentCompare;
}
}
} else {
return StringUtils.compare(o1.getDenominazione(), o2.getDenominazione());
}
return 0;
}
如您所见,我已经使用Apaches StringUtils.compare()和NumberUtils.compere()作为标准帮助。
答案 16 :(得分:0)
修改 this 答案
实施:
import static java.lang.Math.pow;
import java.util.Comparator;
public class AlphanumComparator implements Comparator<String> {
public static final AlphanumComparator ALPHANUM_COMPARATOR = new AlphanumComparator();
private static char[] upperCaseCache = new char[(int) pow(2, 16)];
private boolean nullIsLess;
public AlphanumComparator() {
}
public AlphanumComparator(boolean nullIsLess) {
this.nullIsLess = nullIsLess;
}
@Override
public int compare(String s1, String s2) {
if (s1 == s2)
return 0;
if (s1 == null)
return nullIsLess ? -1 : 1;
if (s2 == null)
return nullIsLess ? 1 : -1;
int i1 = 0;
int i2 = 0;
int len1 = s1.length();
int len2 = s2.length();
while (true) {
// handle the case when one string is longer than another
if (i1 == len1)
return i2 == len2 ? 0 : -1;
if (i2 == len2)
return 1;
char ch1 = s1.charAt(i1);
char ch2 = s2.charAt(i2);
if (isDigit(ch1) && isDigit(ch2)) {
// skip leading zeros
while (i1 < len1 && s1.charAt(i1) == '0')
i1++;
while (i2 < len2 && s2.charAt(i2) == '0')
i2++;
// find the ends of the numbers
int end1 = i1;
int end2 = i2;
while (end1 < len1 && isDigit(s1.charAt(end1)))
end1++;
while (end2 != len2 && isDigit(s2.charAt(end2)))
end2++;
// if the lengths are different, then the longer number is bigger
int diglen1 = end1 - i1;
int diglen2 = end2 - i2;
if (diglen1 != diglen2)
return diglen1 - diglen2;
// compare numbers digit by digit
while (i1 < end1) {
ch1 = s1.charAt(i1);
ch2 = s2.charAt(i2);
if (ch1 != ch2)
return ch1 - ch2;
i1++;
i2++;
}
} else {
ch1 = toUpperCase(ch1);
ch2 = toUpperCase(ch2);
if (ch1 != ch2)
return ch1 - ch2;
i1++;
i2++;
}
}
}
private boolean isDigit(char ch) {
return ch >= 48 && ch <= 57;
}
private char toUpperCase(char ch) {
char cached = upperCaseCache[ch];
if (cached == 0) {
cached = Character.toUpperCase(ch);
upperCaseCache[ch] = cached;
}
return cached;
}
}
答案 17 :(得分:0)
我的问题是我的列表由字母数字字符串(例如C22,C3,C5等),字母字符串(例如A,H,R等)和仅数字(例如99、45等)的组合组成需要按A,C3,C5,C22,H,R,45、99的顺序排序。我也有一些重复项需要删除,因此我只能得到一个条目。
我不仅在处理字符串,还在对对象进行排序,并使用对象中的特定字段来获取正确的顺序。
似乎对我有用的解决方案是:
SortedSet<Code> codeSet;
codeSet = new TreeSet<Code>(new Comparator<Code>() {
private boolean isThereAnyNumber(String a, String b) {
return isNumber(a) || isNumber(b);
}
private boolean isNumber(String s) {
return s.matches("[-+]?\\d*\\.?\\d+");
}
private String extractChars(String s) {
String chars = s.replaceAll("\\d", "");
return chars;
}
private int extractInt(String s) {
String num = s.replaceAll("\\D", "");
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
private int compareStrings(String o1, String o2) {
if (!extractChars(o1).equals(extractChars(o2))) {
return o1.compareTo(o2);
} else
return extractInt(o1) - extractInt(o2);
}
@Override
public int compare(Code a, Code b) {
return isThereAnyNumber(a.getPrimaryCode(), b.getPrimaryCode())
? isNumber(a.getPrimaryCode()) ? 1 : -1
: compareStrings(a.getPrimaryCode(), b.getPrimaryCode());
}
});
它“借用”了我在Stackoverflow上找到的一些代码,以及我自己的一些调整,以使其能够按我的需要工作。
由于试图订购对象,需要一个比较器以及删除重复对象,我不得不采用的一个缺点是我必须先将对象写入TreeMap,然后再将它们写入Treeset。可能会稍微影响性能,但考虑到列表最多可以包含约80个代码,这应该不是问题。
答案 18 :(得分:0)
虽然问题是java解决方案,对于任何想要scala解决方案的人来说:
object Alphanum {
private[this] val regex = "((?<=[0-9])(?=[^0-9]))|((?<=[^0-9])(?=[0-9]))"
private[this] val alphaNum: Ordering[String] = Ordering.fromLessThan((ss1: String, ss2: String) => (ss1, ss2) match {
case (sss1, sss2) if sss1.matches("[0-9]+") && sss2.matches("[0-9]+") => sss1.toLong < sss2.toLong
case (sss1, sss2) => sss1 < sss2
})
def ordering: Ordering[String] = Ordering.fromLessThan((s1: String, s2: String) => {
import Ordering.Implicits.infixOrderingOps
implicit val ord: Ordering[List[String]] = Ordering.Implicits.seqDerivedOrdering(alphaNum)
s1.split(regex).toList < s2.split(regex).toList
})
}
答案 19 :(得分:0)
简短的回答:基于上下文,我不知道这是否仅仅是一些个人使用的快速和肮脏的代码,或者是高盛最新内部会计软件的关键部分,所以我将打开说:eww。这是一个相当时髦的排序算法;如果可以的话,尝试使用一些不那么“扭曲”的东西。
答案很长:
在您的案例中立即浮现的两个问题是绩效和正确性。非正式地,确保它快速,并确保您的算法是total ordering。
(当然,如果你没有排序超过100个项目,你可能会忽略这一段。)性能很重要,因为比较器的速度将是你的排序速度的最大因素(假设排序算法是典型列表的“理想”。在您的情况下,比较器的速度将主要取决于字符串的大小。字符串似乎相当短,因此它们可能不会像列表的大小那样占主导地位。
将每个字符串转换为字符串数字字符串元组,然后按照另一个答案中的建议对这个元组列表进行排序,在某些情况下会失败,因为您显然会出现多个数字的字符串。
另一个问题是正确性。具体而言,如果您描述的算法将允许A&gt; B> ...&gt; 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
答案 20 :(得分:0)
我认为你必须按照逐个字符的方式进行比较。抓住一个角色,如果它是一个数字角色,继续抓取,然后将字符重新组合成一个数字字符串并将其转换为int
。重复另一个字符串,然后才进行比较。
答案 21 :(得分:-1)
在Linux上,glibc提供了strverscmp(),它也可以从gnulib获得,以实现可移植性。然而,真正的“人类”排序有很多其他的怪癖,如“甲壳虫乐队”被归类为“披头士乐队”。这个通用问题没有简单的解决方案。
答案 22 :(得分:-1)
如果您正在编写比较器类,则应实现自己的比较方法,该方法将逐个字符地比较两个字符串。此比较方法应检查您是处理字母字符,数字字符还是混合类型(包括空格)。你必须定义你想要一个混合类型的行为方式,数字是在字母字符之前还是之后,以及空格适合等等。
答案 23 :(得分:-1)
在您给出的示例中,您要比较的数字在它们周围有空格而其他数字没有,那么为什么正则表达式不起作用?
bbb 12 ccc
VS