结合字母顺序和自然顺序(又名。用户理智排序)
Posted
技术标签:
【中文标题】结合字母顺序和自然顺序(又名。用户理智排序)【英文标题】:Combine alphabetical and natural order (aka. User sane sorting) 【发布时间】:2012-09-20 08:30:36 【问题描述】:我认为这很容易找到预制的,但似乎我可以在网上找到的任何解决方案都只能解决部分问题。
我想对用户提供的文件名列表(这些文件大多以个人和/或地址命名)进行排序,有时使用不同的语言(主要是德语,带有有点法语和意大利语混杂在一起,很少有其他西方语言)。
我们的想法是按照(德国)用户通常认为合理的方式排列此列表。这意味着顺序应该遵循 java.text.Collator for Locale.GERMAN,但同时期望对字符串中的数字进行例外处理,因此“10”出现在“2”之后”。
我在网络上找到了进行自然排序的代码,但它依赖于逐个字符的比较(而 Collator 不支持这一点)。我可以用子字符串破解一些东西,但是在比较器中,我认为在每个比较调用上创建多个子字符串并不是最聪明的主意。
任何想法如何有效地实现这一点(在执行时间和实现时间),或者更好的测试和准备使用的实现?
【问题讨论】:
davekoelle.com/alphanum.html @millimoose 链接代码无法正确比较 alphabethic 部分(这就是我明确提到 german locale 的原因)。 我确实打算将链接作为起点。一般来说,人们应该期望审查任何由说英语的人编写的代码处理文本。 【参考方案1】:这是已接受的答案中的改编代码(基于The Alphanum Algorithm)。该代码经过优化以减少垃圾创建并处理前导零 (01
import java.text.Collator;
import java.util.Comparator;
/**
* Comparator for ordering by Collator while treating digits numerically.
* This provides a "natural" order that humans usually perceive as 'logical'.
*
* It should work reasonably well for western languages (provided you
* use the proper collator when constructing). For free control over the
* Collator, use the constructor that takes a Collator as parameter.
* Configure the Collator using Collator.setDecomposition()/setStrength()
* to suit your requirements.
*/
public class AlphanumComparator implements Comparator<CharSequence>
/**
* The collator used for comparison of the alpha part
*/
private final Collator collator;
/**
* Create comparator using platform default collator.
* (equivalent to using Collator.getInstance())
*/
public AlphanumComparator()
this(Collator.getInstance());
/**
* Create comparator using specified collator
*/
public AlphanumComparator(final Collator collator)
if (collator == null)
throw new IllegalArgumentException("collator must not be null");
this.collator = collator;
/**
* Ideally this would be generalized to Character.isDigit(), but I have
* no knowledge about arabic language and other digits, so I treat
* them as characters...
*/
private static boolean isDigit(final int character)
// code between ASCII '0' and '9'?
return character >= 48 && character <= 57;
/**
* Get subsequence of only characters or only digits, but not mixed
*/
private static CharSequence getChunk(final CharSequence charSeq, final int start)
int index = start;
final int length = charSeq.length();
final boolean mode = isDigit(charSeq.charAt(index++));
while (index < length)
if (isDigit(charSeq.charAt(index)) != mode)
break;
++index;
return charSeq.subSequence(start, index);
/**
* Implements Comparator<CharSequence>.compare
*/
public int compare(final CharSequence charSeq1, final CharSequence charSeq2)
final int length1 = charSeq1.length();
final int length2 = charSeq2.length();
int index1 = 0;
int index2 = 0;
int result = 0;
while (result == 0 && index1 < length1 && index2 < length2)
final CharSequence chunk1 = getChunk(charSeq1, index1);
index1 += chunk1.length();
final CharSequence chunk2 = getChunk(charSeq2, index2);
index2 += chunk2.length();
if (isDigit(chunk1.charAt(0)) && isDigit(chunk2.charAt(0)))
final int clen1 = chunk1.length();
final int clen2 = chunk2.length();
// count and skip leading zeros
int zeros1 = 0;
while (zeros1 < clen1 && chunk1.charAt(zeros1) == '0')
++zeros1;
// count and skip leading zeros
int zeros2 = 0;
while (zeros2 < clen2 && chunk2.charAt(zeros2) == '0')
++zeros2;
// the longer run of non-zero digits is greater
result = (clen1 - zeros1) - (clen2 - zeros2);
// if the length is the same, the first differing digit decides
// which one is deemed greater.
int subi1 = zeros1;
int subi2 = zeros2;
while (result == 0 && subi1 < clen1 && subi2 < clen2)
result = chunk1.charAt(subi1++) - chunk2.charAt(subi2++);
// if still no difference, the longer zeros-prefix is greater
if (result == 0)
result = subi1 - subi2;
else
// in case we are working with Strings, toString() doesn't create
// any objects (String.toString() returns the same string itself).
result = collator.compare(chunk1.toString(), chunk2.toString());
// if there was no difference at all, let the longer one be the greater one
if (result == 0)
result = length1 - length2;
// limit result to (-1, 0, or 1)
return Integer.signum(result);
2014-12-01 编辑:Konstantin Petrukhnov 在 cmets 中指出的固定版本。
【讨论】:
我不知道 Steve Kuo 所说的“太多的最终混乱”是什么意思...无论如何,我真的很感谢您回复您的最终解决方案,让我了解所涉及的复杂性。将来某个时候我可能需要这样的东西。 @Zalumon Steve 似乎不喜欢所有东西,甚至可能的局部变量,都被声明为 final。这只是个人品味(或项目代码风格指南)的问题。顺便说一句,看看这个版本,发布的代码中很少触发可能导致 IndexOutOfBoundException 的错误。我用我固定的、高效的代码替换了它。 getChunk() 中有一个小错误。它有时会给出错误的序列。这是固定版本:gist.github.com/petrukhnov/8910522710a69b4d8c6b @KonstantinPetrukhnov 感谢您的指出,显然我很久以前在本地代码中修复了它,但从未更新过帖子。在最新的编辑中更正了它。 伟大的实施!我还对其进行了一些修改/重构,以通过使用Character.isDigit
和Character.getNumericValue
来改进对0123456789
等非ASCII 数字字符的支持:github.com/filebot/filebot/blob/master/source/net/filebot/util/…【参考方案2】:
如果您使用@millimoose (http://www.davekoelle.com/alphanum.html) 建议的 Comparator,请修改它以通过 Collator
public class AlphanumComparator implements Comparator
private Collator collator;
public AlphanumComparator(Collator collator)
this.collator = collator;
.....
public int compare(Object o1, Object o2)
......
result = thisChunk.compareTo(thatChunk); //should become
collator.compare(thisChuck, thatChuck);
....
此代码似乎有问题,例如“01”大于“2”。但这取决于您的偏好,如果这很重要,请修改它以在数字比较之前跳过前导零。
【讨论】:
是的,我也尝试过这种修改。我更喜欢 01 之前 2 (我可以解决这个问题)。我也在 getChunk() 中优化了无意义的 StringBuilder,它在每次调用时保存了 两个 对象创建(构建器 和 子字符串中的 char[])。它仍然创建一个(子)字符串,但性能似乎足以满足我的目的。 我接受了这个,因为它基本上就是我想要的(如果没有更好的结果)。我稍后会为那些偶然发现这个问题的人发布固定/优化/注释代码。以上是关于结合字母顺序和自然顺序(又名。用户理智排序)的主要内容,如果未能解决你的问题,请参考以下文章
使用 NSFetchRequest 按日期和单元格按时区字母顺序对部分进行排序
对字符串中字符进行自然顺序排序(基本类型排序)-冒泡算法实现