Это называется «естественным порядком сортировки» и обычно используется для сортировки таких элементов, как те, которые у вас есть, например, имен файлов и т.п.
Вот наивная (в том смысле, что с ней, вероятно, много проблем с Unicode) реализация, которая, кажется, помогает:
Вы можете скопировать приведенный ниже код в LINQPad, чтобы выполнить и протестировать его.
В основном алгоритм сравнения идентифицирует числа внутри строк и обрабатывает их, дополняя самую короткую из них ведущими нулями, поэтому, например, две строки "Test123Abc"
и "Test7X"
следует сравнивать, как если бы они были "Test123Abc"
и "Test007X"
, что должно дать то, что вы хотите.
Однако, когда я сказал «наивный», я имел в виду, что у меня, вероятно, есть масса настоящих проблем с Unicode, таких как обработка диакритических знаков и символов с несколькими кодовыми точками. Если кто-то может дать лучшую реализацию, я хотел бы ее увидеть.
Примечания:
- Реализация на самом деле не анализирует числа, поэтому произвольно длинные числа должны работать нормально.
- Поскольку на самом деле он не анализирует числа как «числа», числа с плавающей запятой не будут обрабатываться должным образом, «123,45» и «123,789» будут сравниваться как «123,045» и «123,789», что неверно.
Код:
void Main()
{
List<string> input = new List<string>
{
"1", "5", "3", "6", "11", "9", "A1", "A0"
};
var output = input.NaturalSort();
output.Dump();
}
public static class Extensions
{
public static IEnumerable<string> NaturalSort(
this IEnumerable<string> collection)
{
return NaturalSort(collection, CultureInfo.CurrentCulture);
}
public static IEnumerable<string> NaturalSort(
this IEnumerable<string> collection, CultureInfo cultureInfo)
{
return collection.OrderBy(s => s, new NaturalComparer(cultureInfo));
}
private class NaturalComparer : IComparer<string>
{
private readonly CultureInfo _CultureInfo;
public NaturalComparer(CultureInfo cultureInfo)
{
_CultureInfo = cultureInfo;
}
public int Compare(string x, string y)
{
// simple cases
if (x == y) // also handles null
return 0;
if (x == null)
return -1;
if (y == null)
return +1;
int ix = 0;
int iy = 0;
while (ix < x.Length && iy < y.Length)
{
if (Char.IsDigit(x[ix]) && Char.IsDigit(y[iy]))
{
// We found numbers, so grab both numbers
int ix1 = ix++;
int iy1 = iy++;
while (ix < x.Length && Char.IsDigit(x[ix]))
ix++;
while (iy < y.Length && Char.IsDigit(y[iy]))
iy++;
string numberFromX = x.Substring(ix1, ix - ix1);
string numberFromY = y.Substring(iy1, iy - iy1);
// Pad them with 0's to have the same length
int maxLength = Math.Max(
numberFromX.Length,
numberFromY.Length);
numberFromX = numberFromX.PadLeft(maxLength, '0');
numberFromY = numberFromY.PadLeft(maxLength, '0');
int comparison = _CultureInfo
.CompareInfo.Compare(numberFromX, numberFromY);
if (comparison != 0)
return comparison;
}
else
{
int comparison = _CultureInfo
.CompareInfo.Compare(x, ix, 1, y, iy, 1);
if (comparison != 0)
return comparison;
ix++;
iy++;
}
}
// we should not be here with no parts left, they're equal
Debug.Assert(ix < x.Length || iy < y.Length);
// we still got parts of x left, y comes first
if (ix < x.Length)
return +1;
// we still got parts of y left, x comes first
return -1;
}
}
}
person
Lasse V. Karlsen
schedule
15.09.2010