Естественный порядок сортировки в C #

У кого-нибудь есть хороший ресурс или есть образец сортировки в естественном порядке на C # для массива FileInfo? Я реализую IComparer интерфейс в своих сортах.


person Michael Kniskern    schedule 29.10.2008    source источник


Ответы (18)


Проще всего сделать это просто P / Invoke встроенной функции в Windows и использовать ее как функцию сравнения в вашем IComparer:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string psz1, string psz2);

У Майкла Каплана есть несколько примеров того, как эта функция работает здесь, а также изменения. которые были созданы для Vista, чтобы сделать ее работу более интуитивно понятной. Положительной стороной этой функции является то, что она будет вести себя так же, как и версия Windows, в которой она работает, однако это означает, что она различается между версиями Windows, поэтому вам нужно подумать, является ли это проблемой для вас.

Таким образом, полная реализация будет примерно такой:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    public static extern int StrCmpLogicalW(string psz1, string psz2);
}

public sealed class NaturalStringComparer : IComparer<string>
{
    public int Compare(string a, string b)
    {
        return SafeNativeMethods.StrCmpLogicalW(a, b);
    }
}

public sealed class NaturalFileInfoNameComparer : IComparer<FileInfo>
{
    public int Compare(FileInfo a, FileInfo b)
    {
        return SafeNativeMethods.StrCmpLogicalW(a.Name, b.Name);
    }
}
person Greg Beech    schedule 29.10.2008
comment
Отличный ответ. Предостережение: это не будет работать с Win2000 для тех немногих людей, которые все еще работают в этой операционной системе. С другой стороны, между блогом Каплана и документацией MSDN достаточно намеков, чтобы создать аналогичную функцию. - person Chris Charabaruk; 30.10.2008
comment
Это не переносимо, работает только в Win32, но не работает в Linux / MacOS / Silverlight / Windows Phone / Metro. - person linquize; 29.07.2012
comment
@linquize - Он сказал, что .NET не Mono, поэтому Linux / OSX на самом деле не проблема. Windows Phone / Metro не существовало в 2008 году, когда был опубликован этот ответ. А как часто вы выполняете файловые операции в Silverlight? Так что для OP и, вероятно, для большинства других людей это был подходящий ответ. В любом случае вы можете дать лучший ответ; вот как работает этот сайт. - person Greg Beech; 30.07.2012
comment
в названии говорилось, что C #, а не только MS.NET в win32. так что более широкая поддержка платформы может быть актуальной сегодня. - person linquize; 30.07.2012
comment
Это не означает, что исходный ответ был неправильным. Я просто добавляю дополнительную информацию с актуальной информацией - person linquize; 30.07.2012
comment
К вашему сведению, если вы наследуете от Comparer<T> вместо реализации IComparer<T>, вы получите встроенную реализацию IComparer (неуниверсального) интерфейса, который вызывает ваш общий метод для использования в API, которые его используют. По сути, это тоже бесплатно: просто удалите I и измените public int Compare(...) на public override int Compare(...). То же самое для IEqualityComparer<T> и EqualityComparer<T>. - person Joe Amenta; 27.06.2015
comment
Еще одна вещь, которую нужно добавить сюда: BCL StringComparer. * (Т.е. .Ordinal) .Equals (null, null) возвращает true (также известный как '0'), а StrCmpLogicalW (null, null) возвращает -2 / false. Таким образом, реализация NaturalStringComparer не является логически полной / правильной imho. - person Jörg Battermann; 05.07.2018
comment
Все подходы RegEx, даже самые популярные среди пользователей, дают результаты, которые сильно отличаются от естественной сортировки Windows, и большинство пользователей здесь забыли протестировать его на специальных символах. Вызов «StrCmpLogicalW» - это правильный путь, если вам нужна ТОЧНАЯ функциональность естественной сортировки Windows, независимо от ее редкой косвенной ошибки, прокомментированной в одном из ответов в этом потоке. Спасибо за это решение! - person ElektroStudios; 15.03.2021

Просто подумал, что добавлю к этому (с самым кратким решением, которое я смог найти):

public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector)
{
    int max = source
        .SelectMany(i => Regex.Matches(selector(i), @"\d+").Cast<Match>().Select(m => (int?)m.Value.Length))
        .Max() ?? 0;

    return source.OrderBy(i => Regex.Replace(selector(i), @"\d+", m => m.Value.PadLeft(max, '0')));
}

Вышеуказанное дополняет любые числа в строке до максимальной длины всех чисел во всех строках и использует полученную строку для сортировки.

Приведение к (int?) позволяет создавать коллекции строк без каких-либо чисел (.Max() для пустого перечислимого типа выдает InvalidOperationException).

person Matthew Horsley    schedule 30.07.2012
comment
+1 Он не только самый лаконичный, но и самый быстрый, который я когда-либо видел. за исключением принятого ответа, но я не могу его использовать из-за зависимости от машины. Он отсортировал более 4 миллионов значений примерно за 35 секунд. - person Gene S; 05.10.2012
comment
Это красиво и невозможно прочитать. Я предполагаю, что преимущества Linq будут означать (по крайней мере) лучшую среднюю и лучшую производительность, поэтому я думаю, что собираюсь пойти на это. Несмотря на отсутствие ясности. Большое спасибо @Matthew Horsley - person Ian Grainger; 16.01.2014
comment
Это очень хорошо, но есть ошибка для некоторых десятичных чисел, в моем примере сортировка k8.11 vs k8.2. Чтобы исправить это, я реализовал следующее регулярное выражение: \ d + ([\.,] \ D)? - person devzero; 10.03.2016
comment
Вам также необходимо принять во внимание длину второй группы (десятичная точка + десятичные дроби), когда вы вводите этот код m.Value.PadLeft (max, '0') - person devzero; 10.03.2016
comment
Извините, может быть, кратко, но производительность ужасная. На сортировку 1M коротких строк уходит больше секунды, и это действительно очень долгое время. - person markmnl; 01.12.2016
comment
Я думаю, вы можете использовать .DefaultIfEmpty().Max() вместо преобразования в int?. Также стоит сделать source.ToList(), чтобы избежать повторного перечисления. - person Teejay; 22.02.2018
comment
Как бы вы разместили здесь заказ по убыванию? - person testing; 28.02.2018
comment
Какой здесь параметр селектора? - person masterwok; 07.07.2019
comment
Интересно, что это не работает в Linux, даже если я добавлю InvariantCulture в OrderBy, мне нужна реализация, которая дает ту же сортировку в Linux, что и в Windows. В частности, точка стоит перед подчеркиванием в Windows, а в Linux - наоборот. Надеюсь, что одно из других решений здесь сработает :) - person Jesper Niedermann; 25.10.2019
comment
Когда я тестировал это решение, я заметил, что оно не изящно обрабатывает -. Список из {3, 4, -2, 1, 5} сортируется в {1, -2, 3, 4, 5}. - person Anketam; 14.05.2020
comment
Это правильный порядок @Anketam, по крайней мере, в Windows 10. - person Zodman; 24.11.2020

Ни одна из существующих реализаций не выглядела великолепно, поэтому я написал свою собственную. Результаты почти идентичны сортировке, используемой в современных версиях Проводника Windows (Windows 7/8). Единственные различия, которые я видел: 1) хотя раньше Windows (например, XP) обрабатывала числа любой длины, теперь она ограничена 19 цифрами - у меня неограничен, 2) Windows дает несогласованные результаты с определенными наборами цифр Unicode - у меня работает хорошо (хотя он не сравнивает численно цифры из суррогатных пар; как и Windows), и 3) мой не может различать разные типы непервичных весов сортировки, если они встречаются в разных разделах (например, "e-1é" vs " é1e- "- разделы до и после числа имеют разницу в диакритическом знаке и знаке препинания).

public static int CompareNatural(string strA, string strB) {
    return CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
}

public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options) {
    CompareInfo cmp = culture.CompareInfo;
    int iA = 0;
    int iB = 0;
    int softResult = 0;
    int softResultWeight = 0;
    while (iA < strA.Length && iB < strB.Length) {
        bool isDigitA = Char.IsDigit(strA[iA]);
        bool isDigitB = Char.IsDigit(strB[iB]);
        if (isDigitA != isDigitB) {
            return cmp.Compare(strA, iA, strB, iB, options);
        }
        else if (!isDigitA && !isDigitB) {
            int jA = iA + 1;
            int jB = iB + 1;
            while (jA < strA.Length && !Char.IsDigit(strA[jA])) jA++;
            while (jB < strB.Length && !Char.IsDigit(strB[jB])) jB++;
            int cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options);
            if (cmpResult != 0) {
                // Certain strings may be considered different due to "soft" differences that are
                // ignored if more significant differences follow, e.g. a hyphen only affects the
                // comparison if no other differences follow
                string sectionA = strA.Substring(iA, jA - iA);
                string sectionB = strB.Substring(iB, jB - iB);
                if (cmp.Compare(sectionA + "1", sectionB + "2", options) ==
                    cmp.Compare(sectionA + "2", sectionB + "1", options))
                {
                    return cmp.Compare(strA, iA, strB, iB, options);
                }
                else if (softResultWeight < 1) {
                    softResult = cmpResult;
                    softResultWeight = 1;
                }
            }
            iA = jA;
            iB = jB;
        }
        else {
            char zeroA = (char)(strA[iA] - (int)Char.GetNumericValue(strA[iA]));
            char zeroB = (char)(strB[iB] - (int)Char.GetNumericValue(strB[iB]));
            int jA = iA;
            int jB = iB;
            while (jA < strA.Length && strA[jA] == zeroA) jA++;
            while (jB < strB.Length && strB[jB] == zeroB) jB++;
            int resultIfSameLength = 0;
            do {
                isDigitA = jA < strA.Length && Char.IsDigit(strA[jA]);
                isDigitB = jB < strB.Length && Char.IsDigit(strB[jB]);
                int numA = isDigitA ? (int)Char.GetNumericValue(strA[jA]) : 0;
                int numB = isDigitB ? (int)Char.GetNumericValue(strB[jB]) : 0;
                if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false;
                if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false;
                if (isDigitA && isDigitB) {
                    if (numA != numB && resultIfSameLength == 0) {
                        resultIfSameLength = numA < numB ? -1 : 1;
                    }
                    jA++;
                    jB++;
                }
            }
            while (isDigitA && isDigitB);
            if (isDigitA != isDigitB) {
                // One number has more digits than the other (ignoring leading zeros) - the longer
                // number must be larger
                return isDigitA ? 1 : -1;
            }
            else if (resultIfSameLength != 0) {
                // Both numbers are the same length (ignoring leading zeros) and at least one of
                // the digits differed - the first difference determines the result
                return resultIfSameLength;
            }
            int lA = jA - iA;
            int lB = jB - iB;
            if (lA != lB) {
                // Both numbers are equivalent but one has more leading zeros
                return lA > lB ? -1 : 1;
            }
            else if (zeroA != zeroB && softResultWeight < 2) {
                softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options);
                softResultWeight = 2;
            }
            iA = jA;
            iB = jB;
        }
    }
    if (iA < strA.Length || iB < strB.Length) {
        return iA < strA.Length ? 1 : -1;
    }
    else if (softResult != 0) {
        return softResult;
    }
    return 0;
}

Подпись соответствует Comparison<string> делегату:

string[] files = Directory.GetFiles(@"C:\");
Array.Sort(files, CompareNatural);

Вот класс-оболочка для использования как IComparer<string>:

public class CustomComparer<T> : IComparer<T> {
    private Comparison<T> _comparison;

    public CustomComparer(Comparison<T> comparison) {
        _comparison = comparison;
    }

    public int Compare(T x, T y) {
        return _comparison(x, y);
    }
}

Пример:

string[] files = Directory.EnumerateFiles(@"C:\")
    .OrderBy(f => f, new CustomComparer<string>(CompareNatural))
    .ToArray();

Вот хороший набор имен файлов, которые я использую для тестирования:

Func<string, string> expand = (s) => { int o; while ((o = s.IndexOf('\\')) != -1) { int p = o + 1;
    int z = 1; while (s[p] == '0') { z++; p++; } int c = Int32.Parse(s.Substring(p, z));
    s = s.Substring(0, o) + new string(s[o - 1], c) + s.Substring(p + z); } return s; };
string encodedFileNames =
    "KDEqLW4xMiotbjEzKjAwMDFcMDY2KjAwMlwwMTcqMDA5XDAxNyowMlwwMTcqMDlcMDE3KjEhKjEtISox" +
    "LWEqMS4yNT8xLjI1KjEuNT8xLjUqMSoxXDAxNyoxXDAxOCoxXDAxOSoxXDA2NioxXDA2NyoxYSoyXDAx" +
    "NyoyXDAxOCo5XDAxNyo5XDAxOCo5XDA2Nio9MSphMDAxdGVzdDAxKmEwMDF0ZXN0aW5nYTBcMzEqYTAw" +
    "Mj9hMDAyIGE/YTAwMiBhKmEwMDIqYTAwMmE/YTAwMmEqYTAxdGVzdGluZ2EwMDEqYTAxdnNmcyphMSph" +
    "MWEqYTF6KmEyKmIwMDAzcTYqYjAwM3E0KmIwM3E1KmMtZSpjZCpjZipmIDEqZipnP2cgMT9oLW4qaG8t" +
    "bipJKmljZS1jcmVhbT9pY2VjcmVhbT9pY2VjcmVhbS0/ajBcNDE/ajAwMWE/ajAxP2shKmsnKmstKmsx" +
    "KmthKmxpc3QqbTAwMDNhMDA1YSptMDAzYTAwMDVhKm0wMDNhMDA1Km0wMDNhMDA1YSpuMTIqbjEzKm8t" +
    "bjAxMypvLW4xMipvLW40P28tbjQhP28tbjR6P28tbjlhLWI1Km8tbjlhYjUqb24wMTMqb24xMipvbjQ/" +
    "b240IT9vbjR6P29uOWEtYjUqb245YWI1Km/CrW4wMTMqb8KtbjEyKnAwMCpwMDEqcDAxwr0hKnAwMcK9" +
    "KnAwMcK9YSpwMDHCvcK+KnAwMipwMMK9KnEtbjAxMypxLW4xMipxbjAxMypxbjEyKnItMDAhKnItMDAh" +
    "NSpyLTAwIe+8lSpyLTAwYSpyLe+8kFwxIS01KnIt77yQXDEhLe+8lSpyLe+8kFwxISpyLe+8kFwxITUq" +
    "ci3vvJBcMSHvvJUqci3vvJBcMWEqci3vvJBcMyE1KnIwMCEqcjAwLTUqcjAwLjUqcjAwNSpyMDBhKnIw" +
    "NSpyMDYqcjQqcjUqctmg2aYqctmkKnLZpSpy27Dbtipy27Qqctu1KnLfgN+GKnLfhCpy34UqcuClpuCl" +
    "rCpy4KWqKnLgpasqcuCnpuCnrCpy4KeqKnLgp6sqcuCppuCprCpy4KmqKnLgqasqcuCrpuCrrCpy4Kuq" +
    "KnLgq6sqcuCtpuCtrCpy4K2qKnLgrasqcuCvpuCvrCpy4K+qKnLgr6sqcuCxpuCxrCpy4LGqKnLgsasq" +
    "cuCzpuCzrCpy4LOqKnLgs6sqcuC1puC1rCpy4LWqKnLgtasqcuC5kOC5lipy4LmUKnLguZUqcuC7kOC7" +
    "lipy4LuUKnLgu5UqcuC8oOC8pipy4LykKnLgvKUqcuGBgOGBhipy4YGEKnLhgYUqcuGCkOGClipy4YKU" +
    "KnLhgpUqcuGfoOGfpipy4Z+kKnLhn6UqcuGgkOGglipy4aCUKnLhoJUqcuGlhuGljCpy4aWKKnLhpYsq" +
    "cuGnkOGnlipy4aeUKnLhp5UqcuGtkOGtlipy4a2UKnLhrZUqcuGusOGutipy4a60KnLhrrUqcuGxgOGx" +
    "hipy4bGEKnLhsYUqcuGxkOGxlipy4bGUKnLhsZUqcuqYoFwx6pilKnLqmKDqmKUqcuqYoOqYpipy6pik" +
    "KnLqmKUqcuqjkOqjlipy6qOUKnLqo5UqcuqkgOqkhipy6qSEKnLqpIUqcuqpkOqplipy6qmUKnLqqZUq" +
    "cvCQkqAqcvCQkqUqcvCdn5gqcvCdn50qcu+8kFwxISpy77yQXDEt77yVKnLvvJBcMS7vvJUqcu+8kFwx" +
    "YSpy77yQXDHqmKUqcu+8kFwx77yO77yVKnLvvJBcMe+8lSpy77yQ77yVKnLvvJDvvJYqcu+8lCpy77yV" +
    "KnNpKnPEsSp0ZXN02aIqdGVzdNmi2aAqdGVzdNmjKnVBZS0qdWFlKnViZS0qdUJlKnVjZS0xw6kqdWNl" +
    "McOpLSp1Y2Uxw6kqdWPDqS0xZSp1Y8OpMWUtKnVjw6kxZSp3ZWlhMSp3ZWlhMip3ZWlzczEqd2Vpc3My" +
    "KndlaXoxKndlaXoyKndlacOfMSp3ZWnDnzIqeSBhMyp5IGE0KnknYTMqeSdhNCp5K2EzKnkrYTQqeS1h" +
    "Myp5LWE0KnlhMyp5YTQqej96IDA1MD96IDIxP3ohMjE/ejIwP3oyMj96YTIxP3rCqTIxP1sxKl8xKsKt" +
    "bjEyKsKtbjEzKsSwKg==";
string[] fileNames = Encoding.UTF8.GetString(Convert.FromBase64String(encodedFileNames))
    .Replace("*", ".txt?").Split(new[] { "?" }, StringSplitOptions.RemoveEmptyEntries)
    .Select(n => expand(n)).ToArray();
person J.D.    schedule 13.08.2011
comment
Разделы цифр необходимо сравнивать по частям, то есть «abc12b» должно быть меньше, чем «abc123». - person SOUser; 19.02.2013
comment
Вы можете попробовать следующие данные: общедоступная строка [] filenames = {-abc12.txt, abc12.txt, 1abc_2.txt, a0000012.txt, a0000012c.txt, a000012.txt, a000012b.txt, a012.txt, a0000102.txt, abc1_2.txt, abc12 .txt, abc12b.txt, abc123.txt, abccde.txt, b0000.txt, b00001.txt, b0001.txt, b001.txt, c0000.txt, c0000c. txt, c00001.txt, c000b.txt, d0.20.2b.txt, d0.1000c.txt, d0.2000y.txt, d0.20000.2b.txt, e0000120000012, e0000012000012}; - person SOUser; 21.02.2013
comment
@XichenLi Спасибо за хороший тестовый пример. Если вы позволите проводнику Windows сортировать эти файлы, вы получите разные результаты в зависимости от того, какую версию Windows вы используете. Мой код сортирует эти имена идентично Server 2003 (и, предположительно, XP), но отличается от Windows 8. Если у меня будет возможность, я попытаюсь выяснить, как это делает Windows 8, и обновлю свой код. - person J.D.; 21.02.2013
comment
Есть ошибка. Индекс вне диапазона - person linquize; 18.03.2013
comment
Отличное решение! Когда я тестировал его в обычном сценарии с примерно 10 000 файлов, он был быстрее, чем пример регулярного выражения Мэтью, и примерно такой же по производительности, как StrCmpLogicalW (). В приведенном выше коде есть небольшая ошибка: the while (strA [jA] == zeroA) jA ++; а пока (strB [jB] == zeroB) jB ++; должно быть while (jA ‹strA.Length && strA [jA] == zeroA) jA ++; и while (jB ‹strB.Length && strB [jB] == zeroB) jB ++ ;. В противном случае строки, содержащие только нули, вызовут исключение. - person kuroki; 08.12.2015
comment
Спасибо. Я буду использовать это в своей программе. - person Damn Vegetables; 25.12.2015
comment
Куроки, я обнаружил ту же проблему. Хотел бы я обратить внимание на ваш комментарий. Это происходит, когда есть две строки, в которых число начинается с одной и той же позиции, но заканчивается на 0 (например, {"0", "1"} или {"a.000", "a.001"}. Исправление, конечно, заключается в добавлении проверки, как вы предложили. - person Damn Vegetables; 09.01.2016

Чистое решение C # для linq orderby:

http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html

public class NaturalSortComparer<T> : IComparer<string>, IDisposable
{
    private bool isAscending;

    public NaturalSortComparer(bool inAscendingOrder = true)
    {
        this.isAscending = inAscendingOrder;
    }

    #region IComparer<string> Members

    public int Compare(string x, string y)
    {
        throw new NotImplementedException();
    }

    #endregion

    #region IComparer<string> Members

    int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;

        string[] x1, y1;

        if (!table.TryGetValue(x, out x1))
        {
            x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
            table.Add(x, x1);
        }

        if (!table.TryGetValue(y, out y1))
        {
            y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
            table.Add(y, y1);
        }

        int returnVal;

        for (int i = 0; i < x1.Length && i < y1.Length; i++)
        {
            if (x1[i] != y1[i])
            {
                returnVal = PartCompare(x1[i], y1[i]);
                return isAscending ? returnVal : -returnVal;
            }
        }

        if (y1.Length > x1.Length)
        {
            returnVal = 1;
        }
        else if (x1.Length > y1.Length)
        { 
            returnVal = -1; 
        }
        else
        {
            returnVal = 0;
        }

        return isAscending ? returnVal : -returnVal;
    }

    private static int PartCompare(string left, string right)
    {
        int x, y;
        if (!int.TryParse(left, out x))
            return left.CompareTo(right);

        if (!int.TryParse(right, out y))
            return left.CompareTo(right);

        return x.CompareTo(y);
    }

    #endregion

    private Dictionary<string, string[]> table = new Dictionary<string, string[]>();

    public void Dispose()
    {
        table.Clear();
        table = null;
    }
}
person James McCormack    schedule 12.03.2010
comment
В конечном итоге этот код взят из codeproject.com/KB/recipes/NaturalComparer.aspx (который не ориентирован на LINQ). - person mhenry1384; 01.09.2010
comment
В сообщении блога упоминается Джастин Джонс (codeproject.com/KB/string/NaturalSortComparer.aspx) для IComparer, а не Pascal Ganaye. - person James McCormack; 01.09.2010
comment
Незначительное примечание: это решение игнорирует пробелы, которые не совпадают с тем, что делают окна, и не так хорошо, как приведенный ниже код Мэтью Хорсли. Таким образом, вы можете получить, например, 'string01' 'string 01' 'string 02' 'string02' (что выглядит некрасиво). Если вы удалите удаление пробелов, он упорядочит строки в обратном порядке, то есть строка 01 идет перед строкой 01, что может быть или неприемлемо. - person Michael Parker; 07.03.2014
comment
Это работало для адресов, например, 1 Smith Rd, 10 Smith Rd, 2 Smith Rd и т. Д. - Сортировка естественным образом. Да! Хороший! - person Piotr Kula; 12.08.2015
comment
Между прочим, я заметил (и комментарии к этой связанной странице также, кажется, указывают), что аргумент Тип ‹T› совершенно не нужен. - person jv-dev; 02.06.2016

Ответ Мэтьюза Хорсли - это самый быстрый метод, который не меняет поведение в зависимости от того, на какой версии Windows работает ваша программа. Однако это может быть еще быстрее, если создать регулярное выражение один раз и использовать RegexOptions.Compiled. Я также добавил возможность вставки средства сравнения строк, чтобы при необходимости можно было игнорировать регистр, и немного улучшил читаемость.

    public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> items, Func<T, string> selector, StringComparer stringComparer = null)
    {
        var regex = new Regex(@"\d+", RegexOptions.Compiled);

        int maxDigits = items
                      .SelectMany(i => regex.Matches(selector(i)).Cast<Match>().Select(digitChunk => (int?)digitChunk.Value.Length))
                      .Max() ?? 0;

        return items.OrderBy(i => regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), stringComparer ?? StringComparer.CurrentCulture);
    }

Использовать в

var sortedEmployees = employees.OrderByNatural(emp => emp.Name);

Это занимает 450 мс для сортировки 100 000 строк по сравнению с 300 мс для сравнения строк по умолчанию .net - довольно быстро!

person Michael Parker    schedule 11.03.2014
comment
Это стоит прочитать относительно вышеизложенного - Компиляция и повторное использование в регулярных выражениях - person mungflesh; 14.06.2016

Мое решение:

void Main()
{
    new[] {"a4","a3","a2","a10","b5","b4","b400","1","C1d","c1d2"}.OrderBy(x => x, new NaturalStringComparer()).Dump();
}

public class NaturalStringComparer : IComparer<string>
{
    private static readonly Regex _re = new Regex(@"(?<=\D)(?=\d)|(?<=\d)(?=\D)", RegexOptions.Compiled);

    public int Compare(string x, string y)
    {
        x = x.ToLower();
        y = y.ToLower();
        if(string.Compare(x, 0, y, 0, Math.Min(x.Length, y.Length)) == 0)
        {
            if(x.Length == y.Length) return 0;
            return x.Length < y.Length ? -1 : 1;
        }
        var a = _re.Split(x);
        var b = _re.Split(y);
        int i = 0;
        while(true)
        {
            int r = PartCompare(a[i], b[i]);
            if(r != 0) return r;
            ++i;
        }
    }

    private static int PartCompare(string x, string y)
    {
        int a, b;
        if(int.TryParse(x, out a) && int.TryParse(y, out b))
            return a.CompareTo(b);
        return x.CompareTo(y);
    }
}

Результаты:

1
a2
a3
a4
a10
b4
b5
b400
C1d
c1d2
person mpen    schedule 24.07.2012
comment
Мне это нравится. Это легко понять и не требует Linq. - person ; 02.07.2016
comment
Это вызывает IndexOutOfRangeException для массива {"1", "01"}. - person isanae; 30.03.2021

Вам нужно быть осторожным - я смутно помню, что читал, что StrCmpLogicalW или что-то в этом роде не было строго транзитивным, и я наблюдал, что методы сортировки .NET иногда застревают в бесконечных циклах, если функция сравнения нарушает это правило.

Транзитивное сравнение всегда будет сообщать, что a ‹c, если a‹ b и b ‹c. Существует функция, которая выполняет естественное сравнение порядка сортировки, которое не всегда соответствует этому критерию, но я не могу вспомнить, является ли это StrCmpLogicalW или что-то еще.

person Jonathan Gilbert    schedule 01.09.2009
comment
У вас есть доказательства этого утверждения? После поиска в Google я не могу найти никаких указаний на то, что это правда. - person mhenry1384; 01.09.2010
comment
Я испытал эти бесконечные циклы с StrCmpLogicalW. - person thd; 28.12.2010
comment
Элемент обратной связи Visual Studio 236900 больше не существует, но вот более свежий, подтверждающий проблему: connect.microsoft.com/VisualStudio/feedback/details/774540/ Это также дает работу- около: CultureInfo имеет свойство CompareInfo, и возвращаемый им объект может предоставить вам SortKey объектов. Их, в свою очередь, можно сравнивать и гарантировать транзитивность. - person Jonathan Gilbert; 08.05.2015

Это мой код для сортировки строки, содержащей как буквенные, так и числовые символы.

Во-первых, это метод расширения:

public static IEnumerable<string> AlphanumericSort(this IEnumerable<string> me)
{
    return me.OrderBy(x => Regex.Replace(x, @"\d+", m => m.Value.PadLeft(50, '0')));
}

Затем просто используйте его в любом месте вашего кода следующим образом:

List<string> test = new List<string>() { "The 1st", "The 12th", "The 2nd" };
test = test.AlphanumericSort();

Как это работает ? Заменив нулями:

  Original  | Regex Replace |      The      |   Returned
    List    | Apply PadLeft |    Sorting    |     List
            |               |               |
 "The 1st"  |  "The 001st"  |  "The 001st"  |  "The 1st"
 "The 12th" |  "The 012th"  |  "The 002nd"  |  "The 2nd"
 "The 2nd"  |  "The 002nd"  |  "The 012th"  |  "The 12th"

Работает с кратными числами:

 Alphabetical Sorting | Alphanumeric Sorting
                      |
 "Page 21, Line 42"   | "Page 3, Line 7"
 "Page 21, Line 5"    | "Page 3, Line 32"
 "Page 3, Line 32"    | "Page 21, Line 5"
 "Page 3, Line 7"     | "Page 21, Line 42"

Надеюсь, это поможет.

person Picsonald    schedule 27.10.2016
comment
Как это работает? Заменяя нулями ... "The 001st" Я имею в виду, честно говоря, вы используете на много нулей больше, чем это ("The 00000000000000000000000000000000000000000000000001st"), и по сути это тот же ответ, что и у Мэтью Хорсли (ага, хотя вы наивны насчет максимальной длины числа, что может быть плохо), но это хороший код гольф и отличное объяснение. ; ^ D - person ruffin; 13.11.2020

Добавление к ответу Грега Бича (потому что я только что искал that), если вы хотите использовать это из Linq, вы можете использовать OrderBy, который принимает IComparer. Например.:

var items = new List<MyItem>();

// fill items

var sorted = items.OrderBy(item => item.Name, new NaturalStringComparer());
person Wilka    schedule 23.09.2009

Вот относительно простой пример, который не использует P / Invoke и позволяет избежать выделения памяти во время выполнения.

Не стесняйтесь использовать код отсюда, или, если проще, есть пакет NuGet:

https://www.nuget.org/packages/NaturalSort

https://github.com/drewnoakes/natural-sort

internal sealed class NaturalStringComparer : IComparer<string>
{
    public static NaturalStringComparer Instance { get; } = new NaturalStringComparer();

    public int Compare(string x, string y)
    {
        // sort nulls to the start
        if (x == null)
            return y == null ? 0 : -1;
        if (y == null)
            return 1;

        var ix = 0;
        var iy = 0;

        while (true)
        {
            // sort shorter strings to the start
            if (ix >= x.Length)
                return iy >= y.Length ? 0 : -1;
            if (iy >= y.Length)
                return 1;

            var cx = x[ix];
            var cy = y[iy];

            int result;
            if (char.IsDigit(cx) && char.IsDigit(cy))
                result = CompareInteger(x, y, ref ix, ref iy);
            else
                result = cx.CompareTo(y[iy]);

            if (result != 0)
                return result;

            ix++;
            iy++;
        }
    }

    private static int CompareInteger(string x, string y, ref int ix, ref int iy)
    {
        var lx = GetNumLength(x, ix);
        var ly = GetNumLength(y, iy);

        // shorter number first (note, doesn't handle leading zeroes)
        if (lx != ly)
            return lx.CompareTo(ly);

        for (var i = 0; i < lx; i++)
        {
            var result = x[ix++].CompareTo(y[iy++]);
            if (result != 0)
                return result;
        }

        return 0;
    }

    private static int GetNumLength(string s, int i)
    {
        var length = 0;
        while (i < s.Length && char.IsDigit(s[i++]))
            length++;
        return length;
    }
}

Он не игнорирует ведущие нули, поэтому 01 идет после 2.

Соответствующий модульный тест:

public class NumericStringComparerTests
{
    [Fact]
    public void OrdersCorrectly()
    {
        AssertEqual("", "");
        AssertEqual(null, null);
        AssertEqual("Hello", "Hello");
        AssertEqual("Hello123", "Hello123");
        AssertEqual("123", "123");
        AssertEqual("123Hello", "123Hello");

        AssertOrdered("", "Hello");
        AssertOrdered(null, "Hello");
        AssertOrdered("Hello", "Hello1");
        AssertOrdered("Hello123", "Hello124");
        AssertOrdered("Hello123", "Hello133");
        AssertOrdered("Hello123", "Hello223");
        AssertOrdered("123", "124");
        AssertOrdered("123", "133");
        AssertOrdered("123", "223");
        AssertOrdered("123", "1234");
        AssertOrdered("123", "2345");
        AssertOrdered("0", "1");
        AssertOrdered("123Hello", "124Hello");
        AssertOrdered("123Hello", "133Hello");
        AssertOrdered("123Hello", "223Hello");
        AssertOrdered("123Hello", "1234Hello");
    }

    private static void AssertEqual(string x, string y)
    {
        Assert.Equal(0, NaturalStringComparer.Instance.Compare(x, y));
        Assert.Equal(0, NaturalStringComparer.Instance.Compare(y, x));
    }

    private static void AssertOrdered(string x, string y)
    {
        Assert.Equal(-1, NaturalStringComparer.Instance.Compare(x, y));
        Assert.Equal( 1, NaturalStringComparer.Instance.Compare(y, x));
    }
}
person Drew Noakes    schedule 15.12.2016

Я фактически реализовал это как метод расширения на StringComparer, чтобы вы могли, например, сделать:

  • StringComparer.CurrentCulture.WithNaturalSort() or
  • StringComparer.OrdinalIgnoreCase.WithNaturalSort().

Полученный IComparer<string> можно использовать во всех местах, таких как OrderBy, OrderByDescending, ThenBy, ThenByDescending, SortedSet<string> и т. Д. И вы все равно можете легко настроить чувствительность к регистру, культуру и т. Д.

Реализация довольно тривиальна и должна хорошо работать даже с большими последовательностями.


Я также опубликовал его как крошечный пакет NuGet , так что вы можете просто сделать:

Install-Package NaturalSort.Extension

Код, включая комментарии XML-документации и набор тестов доступен в репозитории GitHub .


Вот весь код (если вы еще не можете использовать C # 7, просто установите пакет NuGet):

public static class StringComparerNaturalSortExtension
{
    public static IComparer<string> WithNaturalSort(this StringComparer stringComparer) => new NaturalSortComparer(stringComparer);

    private class NaturalSortComparer : IComparer<string>
    {
        public NaturalSortComparer(StringComparer stringComparer)
        {
            _stringComparer = stringComparer;
        }

        private readonly StringComparer _stringComparer;
        private static readonly Regex NumberSequenceRegex = new Regex(@"(\d+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
        private static string[] Tokenize(string s) => s == null ? new string[] { } : NumberSequenceRegex.Split(s);
        private static ulong ParseNumberOrZero(string s) => ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0;

        public int Compare(string s1, string s2)
        {
            var tokens1 = Tokenize(s1);
            var tokens2 = Tokenize(s2);

            var zipCompare = tokens1.Zip(tokens2, TokenCompare).FirstOrDefault(x => x != 0);
            if (zipCompare != 0)
                return zipCompare;

            var lengthCompare = tokens1.Length.CompareTo(tokens2.Length);
            return lengthCompare;
        }
        
        private int TokenCompare(string token1, string token2)
        {
            var number1 = ParseNumberOrZero(token1);
            var number2 = ParseNumberOrZero(token2);

            var numberCompare = number1.CompareTo(number2);
            if (numberCompare != 0)
                return numberCompare;

            var stringCompare = _stringComparer.Compare(token1, token2);
            return stringCompare;
        }
    }
}
person Tom Pažourek    schedule 20.11.2017

Вдохновленный решением Майкла Паркера, вот реализация IComparer, которую вы можете применить к любому из методов упорядочивания linq:

private class NaturalStringComparer : IComparer<string>
{
    public int Compare(string left, string right)
    {
        int max = new[] { left, right }
            .SelectMany(x => Regex.Matches(x, @"\d+").Cast<Match>().Select(y => (int?)y.Value.Length))
            .Max() ?? 0;

        var leftPadded = Regex.Replace(left, @"\d+", m => m.Value.PadLeft(max, '0'));
        var rightPadded = Regex.Replace(right, @"\d+", m => m.Value.PadLeft(max, '0'));

        return string.Compare(leftPadded, rightPadded);
    }
}
person Oliver    schedule 13.09.2018

Вот наивный однострочный способ LINQ без регулярных выражений (заимствованный из python):

var alphaStrings = new List<string>() { "10","2","3","4","50","11","100","a12","b12" };
var orderedString = alphaStrings.OrderBy(g => new Tuple<int, string>(g.ToCharArray().All(char.IsDigit)? int.Parse(g) : int.MaxValue, g));
// Order Now: ["2","3","4","10","11","50","100","a12","b12"]
person mshsayem    schedule 23.04.2018
comment
Удален Dump () и назначен var, и это работает как шарм! - person Arne S; 09.10.2018
comment
@ArneS: Это было написано на LinQPad; и я забыл удалить Dump(). Спасибо, что указали. - person mshsayem; 09.10.2018

Расширяя пару предыдущих ответов и используя методы расширения, я придумал следующее, в котором нет предостережений в отношении потенциального множественного перечислимого перечисления или проблем с производительностью, связанных с использованием нескольких объектов регулярных выражений или ненужным вызовом регулярного выражения, что как говорится, он использует ToList (), что может свести на нет преимущества в больших коллекциях.

Селектор поддерживает универсальную типизацию, чтобы разрешить назначение любого делегата, элементы в исходной коллекции изменяются селектором, а затем преобразуются в строки с помощью ToString ().

    private static readonly Regex _NaturalOrderExpr = new Regex(@"\d+", RegexOptions.Compiled);

    public static IEnumerable<TSource> OrderByNatural<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        int max = 0;

        var selection = source.Select(
            o =>
            {
                var v = selector(o);
                var s = v != null ? v.ToString() : String.Empty;

                if (!String.IsNullOrWhiteSpace(s))
                {
                    var mc = _NaturalOrderExpr.Matches(s);

                    if (mc.Count > 0)
                    {
                        max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                    }
                }

                return new
                {
                    Key = o,
                    Value = s
                };
            }).ToList();

        return
            selection.OrderBy(
                o =>
                String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                     .Select(o => o.Key);
    }

    public static IEnumerable<TSource> OrderByDescendingNatural<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        int max = 0;

        var selection = source.Select(
            o =>
            {
                var v = selector(o);
                var s = v != null ? v.ToString() : String.Empty;

                if (!String.IsNullOrWhiteSpace(s))
                {
                    var mc = _NaturalOrderExpr.Matches(s);

                    if (mc.Count > 0)
                    {
                        max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                    }
                }

                return new
                {
                    Key = o,
                    Value = s
                };
            }).ToList();

        return
            selection.OrderByDescending(
                o =>
                String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                     .Select(o => o.Key);
    }
person Vorspire    schedule 23.09.2014

Вот версия для .NET Core 2.1+ / .NET 5.0+, использующая промежутки, чтобы избежать выделения

public class NaturalSortStringComparer : IComparer<string>
{
    public static NaturalSortStringComparer Ordinal { get; } = new NaturalSortStringComparer(StringComparison.Ordinal);
    public static NaturalSortStringComparer OrdinalIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.OrdinalIgnoreCase);
    public static NaturalSortStringComparer CurrentCulture { get; } = new NaturalSortStringComparer(StringComparison.CurrentCulture);
    public static NaturalSortStringComparer CurrentCultureIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.CurrentCultureIgnoreCase);
    public static NaturalSortStringComparer InvariantCulture { get; } = new NaturalSortStringComparer(StringComparison.InvariantCulture);
    public static NaturalSortStringComparer InvariantCultureIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.InvariantCultureIgnoreCase);

    private readonly StringComparison _comparison;

    public NaturalSortStringComparer(StringComparison comparison)
    {
        _comparison = comparison;
    }

    public int Compare(string? x, string? y)
    {
        // Let string.Compare handle the case where x or y is null
        if (x is null || y is null)
            return string.Compare(x, y, _comparison);

        var xSegments = GetSegments(x);
        var ySegments = GetSegments(y);

        while (xSegments.MoveNext() && ySegments.MoveNext())
        {
            bool xIsNumber = int.TryParse(xSegments.Current, out var xValue);
            bool yIsNumber = int.TryParse(ySegments.Current, out var yValue);

            int cmp;

            // If they're both numbers, compare the value
            if (xIsNumber && yIsNumber)
            {
                cmp = xValue.CompareTo(yValue);
                if (cmp != 0)
                    return cmp;
            }
            // If x is a number and y is not, x is "lesser than" y
            else if (xIsNumber)
            {
                return -1;
            }
            // If y is a number and x is not, x is "greater than" y
            else if (yIsNumber)
            {
                return 1;
            }

            // OK, neither are number, compare the segments as text
            cmp = xSegments.Current.CompareTo(ySegments.Current, _comparison);
            if (cmp != 0)
                return cmp;
        }

        // At this point, either all segments are equal, or one string is shorter than the other

        // If x is shorter, it's "lesser than" y
        if (x.Length < y.Length)
            return -1;
        // If x is longer, it's "greater than" y
        if (x.Length > y.Length)
            return 1;

        // If they have the same length, they're equal
        return 0;
    }

    private static StringSegmentEnumerator GetSegments(string s) => new StringSegmentEnumerator(s);

    private struct StringSegmentEnumerator
    {
        private readonly string _s;
        private int _start;
        private int _length;
        private int _currentPosition;

        public StringSegmentEnumerator(string s)
        {
            _s = s;
            _start = -1;
            _length = 0;
            _currentPosition = 0;
        }

        public ReadOnlySpan<char> Current => _s.AsSpan(_start, _length);

        public bool MoveNext()
        {
            if (_currentPosition >= _s.Length)
                return false;

            int start = _currentPosition;
            bool isFirstCharDigit = Char.IsDigit(_s[_currentPosition]);

            while (++_currentPosition < _s.Length && Char.IsDigit(_s[_currentPosition]) == isFirstCharDigit)
            {
            }

            _start = start;
            _length = _currentPosition - start;
            return true;
        }
    }
}
person Thomas Levesque    schedule 24.02.2021

Нам потребовалась естественная сортировка для работы с текстом по следующему шаблону:

"Test 1-1-1 something"
"Test 1-2-3 something"
...

По какой-то причине, когда я впервые посмотрел на SO, я не нашел этот пост и реализовал наш собственный. По сравнению с некоторыми из представленных здесь решений, хотя они схожи по концепции, они могли бы иметь то преимущество, что они, возможно, были более простыми и понятными. Однако, хотя я и попытался изучить узкие места в производительности, это все еще гораздо более медленная реализация, чем стандартная OrderBy().

Вот метод расширения, который я реализую:

public static class EnumerableExtensions
{
    // set up the regex parser once and for all
    private static readonly Regex Regex = new Regex(@"\d+|\D+", RegexOptions.Compiled | RegexOptions.Singleline);

    // stateless comparer can be built once
    private static readonly AggregateComparer Comparer = new AggregateComparer();

    public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> source, Func<T, string> selector)
    {
        // first extract string from object using selector
        // then extract digit and non-digit groups
        Func<T, IEnumerable<IComparable>> splitter =
            s => Regex.Matches(selector(s))
                      .Cast<Match>()
                      .Select(m => Char.IsDigit(m.Value[0]) ? (IComparable) int.Parse(m.Value) : m.Value);
        return source.OrderBy(splitter, Comparer);
    }

    /// <summary>
    /// This comparer will compare two lists of objects against each other
    /// </summary>
    /// <remarks>Objects in each list are compare to their corresponding elements in the other
    /// list until a difference is found.</remarks>
    private class AggregateComparer : IComparer<IEnumerable<IComparable>>
    {
        public int Compare(IEnumerable<IComparable> x, IEnumerable<IComparable> y)
        {
            return
                x.Zip(y, (a, b) => new {a, b})              // walk both lists
                 .Select(pair => pair.a.CompareTo(pair.b))  // compare each object
                 .FirstOrDefault(result => result != 0);    // until a difference is found
        }
    }
}

Идея состоит в том, чтобы разбить исходные строки на блоки цифр и нецифровые блоки ("\d+|\D+"). Поскольку это потенциально дорогостоящая задача, она выполняется только один раз для каждой записи. Затем мы используем средство сравнения сопоставимых объектов (извините, я не могу найти более точный способ сказать это). Он сравнивает каждый блок с соответствующим блоком в другой строке.

Я хотел бы получить отзывы о том, как это можно улучшить и каковы основные недостатки. Обратите внимание, что ремонтопригодность важна для нас на данном этапе, и в настоящее время мы не используем ее в очень больших наборах данных.

person Eric Liprandi    schedule 21.03.2013
comment
Это дает сбой, когда он пытается сравнить строки, которые структурно различаются - например, сравнение a-1 с a-2 работает нормально, но сравнение a с 1 - нет, потому что a.CompareTo (1) выдает исключение. - person jimrandomh; 29.10.2013
comment
@jimrandomh, вы правы. Такой подход был характерен для наших паттернов. - person Eric Liprandi; 08.11.2013

Версия, которую легче читать / поддерживать.

public class NaturalStringComparer : IComparer<string>
{
    public static NaturalStringComparer Instance { get; } = new NaturalStringComparer();

    public int Compare(string x, string y) {
        const int LeftIsSmaller = -1;
        const int RightIsSmaller = 1;
        const int Equal = 0;

        var leftString = x;
        var rightString = y;

        var stringComparer = CultureInfo.CurrentCulture.CompareInfo;

        int rightIndex;
        int leftIndex;

        for (leftIndex = 0, rightIndex = 0;
             leftIndex < leftString.Length && rightIndex < rightString.Length;
             leftIndex++, rightIndex++) {
            var leftChar = leftString[leftIndex];
            var rightChar = rightString[leftIndex];

            var leftIsNumber = char.IsNumber(leftChar);
            var rightIsNumber = char.IsNumber(rightChar);

            if (!leftIsNumber && !rightIsNumber) {
                var result = stringComparer.Compare(leftString, leftIndex, 1, rightString, leftIndex, 1);
                if (result != 0) return result;
            } else if (leftIsNumber && !rightIsNumber) {
                return LeftIsSmaller;
            } else if (!leftIsNumber && rightIsNumber) {
                return RightIsSmaller;
            } else {
                var leftNumberLength = NumberLength(leftString, leftIndex, out var leftNumber);
                var rightNumberLength = NumberLength(rightString, rightIndex, out var rightNumber);

                if (leftNumberLength < rightNumberLength) {
                    return LeftIsSmaller;
                } else if (leftNumberLength > rightNumberLength) {
                    return RightIsSmaller;
                } else {
                    if(leftNumber < rightNumber) {
                        return LeftIsSmaller;
                    } else if(leftNumber > rightNumber) {
                        return RightIsSmaller;
                    }
                }
            }
        }

        if (leftString.Length < rightString.Length) {
            return LeftIsSmaller;
        } else if(leftString.Length > rightString.Length) {
            return RightIsSmaller;
        }

        return Equal;
    }

    public int NumberLength(string str, int offset, out int number) {
        if (string.IsNullOrWhiteSpace(str)) throw new ArgumentNullException(nameof(str));
        if (offset >= str.Length) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be less than the length of the string.");

        var currentOffset = offset;

        var curChar = str[currentOffset];

        if (!char.IsNumber(curChar))
            throw new ArgumentException($"'{curChar}' is not a number.", nameof(offset));

        int length = 1;

        var numberString = string.Empty;

        for (currentOffset = offset + 1;
            currentOffset < str.Length;
            currentOffset++, length++) {

            curChar = str[currentOffset];
            numberString += curChar;

            if (!char.IsNumber(curChar)) {
                number = int.Parse(numberString);

                return length;
            }
        }

        number = int.Parse(numberString);

        return length;
    }
}
person Kelly Elton    schedule 10.10.2019

Позвольте мне объяснить мою проблему и то, как я смог ее решить.

Проблема: - Сортировка файлов на основе FileName из объектов FileInfo, извлеченных из каталога.

Решение: - Я выбрал имена файлов из FileInfo и выделил часть имени файла «.png». Теперь просто выполните List.Sort (), который сортирует имена файлов в естественном порядке сортировки. Основываясь на моем тестировании, я обнаружил, что наличие .png нарушает порядок сортировки. Взгляните на приведенный ниже код

var imageNameList = new DirectoryInfo(@"C:\Temp\Images").GetFiles("*.png").Select(x =>x.Name.Substring(0, x.Name.Length - 4)).ToList();
imageNameList.Sort();
person girishkatta9    schedule 15.11.2018
comment
Могу ли я узнать причину «-1» в этом ответе? - person girishkatta9; 16.11.2018