Как насчет:
list.Sort((x, y) =>
{
int ix, iy;
return int.TryParse(x, out ix) && int.TryParse(y, out iy)
? ix.CompareTo(iy) : string.Compare(x, y);
});
Это самый быстрый алгоритм - мне потребовалось 2 мили для сортировки 50 элементов ~
static void Sort()
{
string[] partNumbers = new string[] {"A1", "A2", "A10", "A111"};
string[] result = partNumbers.OrderBy(x => PadNumbers(x)).ToArray();
}
public static string PadNumbers(string input)
{
const int MAX_NUMBER_LEN = 10;
string newInput = "";
string currentNumber = "";
foreach (char a in input)
{
if (!char.IsNumber(a))
{
if (currentNumber == "")
{
newInput += a;
continue;
}
newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber;
currentNumber = "";
}
currentNumber += a;
}
if (currentNumber != "")
{
newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber;
}
return newInput;
}
~
Вот решение C # 7 (при условии, что список имеет имя a):
var numericList = a.Where(i => int.TryParse(i, out _)).OrderBy(j => int.Parse(j)).ToList();
var nonNumericList = a.Where(i => !int.TryParse(i, out _)).OrderBy(j => j).ToList();
a.Clear();
a.AddRange(numericList);
a.AddRange(nonNumericList);
Я не думаю, что вам нужно что-то помимо listName.Sort (), потому что метод sort () использует сравнение по умолчанию с узлами быстрой сортировки. Сравнение по умолчанию делает именно то, что вас интересует.
Попробуйте написать небольшой вспомогательный класс для разбора и представления ваших токенов. Например, без слишком большого количества проверок:
public class NameAndNumber
{
public NameAndNumber(string s)
{
OriginalString = s;
Match match = Regex.Match(s,@"^(.*?)(\d*)$");
Name = match.Groups[1].Value;
int number;
int.TryParse(match.Groups[2].Value, out number);
Number = number; //will get default value when blank
}
public string OriginalString { get; private set; }
public string Name { get; private set; }
public int Number { get; private set; }
}
Теперь становится проще записывать компаратор или сортировать его вручную:
var list = new List<string> { "ABC", "1", "5", "NUM44", "3",
"6", "11", "9", "NUM1", "NUM0" };
var sorted = list.Select(str => new NameAndNumber(str))
.OrderBy(n => n.Name)
.ThenBy(n => n.Number);
Дает результат:
1, 3, 5, 6, 9, 11, ABC, NUM0, NUM1, NUM44
blockquote>
Это называется «естественным порядком сортировки» и обычно используется для сортировки таких элементов, как те, которые у вас есть, например, имена файлов и т. д.
Вот наивный (в том смысле, что, вероятно, unicode-problems с ним), которая, похоже, делает трюк:
Вы можете скопировать код ниже в LINQPad , чтобы выполнить его и проверить.
В основном алгоритм сравнения будет идентифицировать числа внутри строк и обрабатывать их путем заполнения самого короткого с ведущими нулями, поэтому, например, две строки "Test123Abc"
и "Test7X"
следует сравнивать, как если бы они были "Test123Abc"
и "Test007X"
, который должен производить то, что вы хотите.
Однако, когда я сказал «наивно», я имею в виду, что у меня, вероятно, есть тонны реальных проблем с Юникодом, например, обработка диакритических знаков и символов с несколькими кодовыми точками.
Примечания:
Код:
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;
}
}
}
Jeff Atwood имеет сообщение в блоге о естественной сортировке, где он ссылается на некоторые доступные реализации желаемого алгоритма.
Один из ссылок Джеффса указывает на Дэйв Коэль , как реализована реализация C # :
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like
* a standard sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* Based on the Java implementation of Dave Koelle's Alphanum algorithm.
* Contributed by Jonathan Ruckwood <jonathan.ruckwood@gmail.com>
*
* Adapted by Dominik Hurnaus <dominik.hurnaus@gmail.com> to
* - correctly sort words where one word starts with another word
* - have slightly better performance
*
* Released under the MIT License - https://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
using System;
using System.Collections;
using System.Text;
/*
* Please compare against the latest Java version at http://www.DaveKoelle.com
* to see the most recent modifications
*/
namespace AlphanumComparator
{
public class AlphanumComparator : IComparer
{
private enum ChunkType {Alphanumeric, Numeric};
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
}