Выполнение стандартного списка Linq Orderby содержит целочисленное и строковое значение [duplicate]

ffill теперь имеет свой собственный метод pd.DataFrame.ffill

df.ffill()

     0    1    2
0  1.0  2.0  3.0
1  4.0  2.0  3.0
2  4.0  2.0  9.0

114
задан Michael Kniskern 28 October 2009 в 16:56
поделиться

16 ответов

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

var items = new List<MyItem>();

// fill items

var sorted = items.OrderBy(item => item.Name, new NaturalStringComparer());
127
ответ дан Community 20 August 2018 в 18:46
поделиться
  • 1
    Отличный ответ. Caveat: Это не будет работать с Win2000, поскольку эти немногие люди все еще работают над этой операционной системой. С другой стороны, есть достаточно советов между блогом Kaplan и документацией MSDN для создания аналогичной функции. – Chris Charabaruk 29 October 2008 в 23:30
  • 2
    Это не переносимо, работает только в Win32, но не работает в Linux / MacOS / Silverlight / Windows Phone / Metro – linquize 29 July 2012 в 10:36
  • 3
    @linquize - Он сказал, что .NET не Моно, поэтому Linux / OSX на самом деле не вызывает беспокойства. Windows Phone / Metro не существует в 2008 году, когда был опубликован этот ответ. И как часто вы выполняете операции с файлами в Silverlight? Так что для ОП и, вероятно, для большинства других людей это был подходящий ответ. В любом случае, вы можете предоставить лучший ответ; вот как работает этот сайт. – Greg Beech 30 July 2012 в 08:45
  • 4
    Это не означает, что исходный ответ был неправильным. Я просто добавляю дополнительную информацию с актуальной информацией – linquize 30 July 2012 в 16:10
  • 5
    FYI, если вы наследуете Comparer<T> вместо реализации IComparer<T>, вы получаете встроенную реализацию IComparer (не общего) интерфейса, который вызывает ваш общий метод, для использования в API, которые используют это вместо этого. В принципе, это тоже можно сделать: просто удалите & quot; I & quot; и измените public int Compare(...) на public override int Compare(...). То же самое для IEqualityComparer<T> и EqualityComparer<T>. – Joe Amenta 27 June 2015 в 01:40
129
ответ дан Community 31 October 2018 в 14:28
поделиться

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

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

    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, NumericStringComparer.Instance.Compare(x, y));
        Assert.Equal(0, NumericStringComparer.Instance.Compare(y, x));
    }

    private static void AssertOrdered(string x, string y)
    {
        Assert.Equal(-1, NumericStringComparer.Instance.Compare(x, y));
        Assert.Equal( 1, NumericStringComparer.Instance.Compare(y, x));
    }
}
2
ответ дан Drew Noakes 20 August 2018 в 18:46
поделиться

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

"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+"). Поскольку это потенциально дорогостоящая задача, она выполняется только один раз для каждой записи. Затем мы используем сопоставитель сопоставимых объектов (извините, я не могу найти более правильный способ сказать это). Он сравнивает каждый блок с соответствующим блоком в другой строке.

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

0
ответ дан Eric Liprandi 20 August 2018 в 18:46
поделиться
  • 1
    Это сбой, когда он пытается сравнить строки, которые являются структурно разными - например, сравнение «a-1» к "a-2" работает отлично, но сравнение "a" к "1" не является, поскольку «a» .CompareTo (1) генерирует исключение. – jimrandomh 29 October 2013 в 20:22
  • 2
    @jimrandomh, вы правы. Этот подход был специфичен для наших моделей. – Eric Liprandi 8 November 2013 в 18:25

Если ваш конечный код предназначен для Интернета (ASP.NET и т. д.), то естественная сортировка может быть достигнута с помощью функции javascript localCampare

'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})

https://stackoverflow.com/a/ 38641281/952018

0
ответ дан Gayan Dasanayake 20 August 2018 в 18:46
поделиться

Ни одна из существующих реализаций не выглядела великолепно, поэтому я написал свои собственные. Результаты почти идентичны сортировке, используемой современными версиями Windows Explorer (Windows 7/8). Единственные отличия, которые я видел, - это 1) хотя Windows, используемая для (например, XP), обрабатывает номера любой длины, теперь она ограничена 19 цифрами - моя не ограничена, 2) Windows дает непоследовательные результаты с определенными наборами цифр Юникода - мои работы (хотя он не численно не сравнивает цифры с суррогатными парами, ни Windows), и 3) мои не могут отличить разные типы непервичных весов сортировки, если они встречаются в разных разделах (например, «e-1е» против « é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();
29
ответ дан Ian Kemp 20 August 2018 в 18:46
поделиться
  • 1
    Секции цифр нужно сравнивать по секциям, т. Е. «Abc12b» должно быть меньше «abc123». – SOUser 19 February 2013 в 00:14
  • 2
    @XichenLi Код уже делает это. – J.D. 20 February 2013 в 08:15
  • 3
    @XichenLi Я обновил свой код, чтобы больше походить на Windows 8. – J.D. 25 February 2013 в 03:22
  • 4
    Есть ошибка. Индекс вне диапазона – linquize 18 March 2013 в 08:23
  • 5
    Отличное решение! Когда я сравнивал его с обычным сценарием с примерно 10 000 файлов, он был быстрее, чем пример регулярного выражения Мэтью, и о той же производительности, что и StrCmpLogicalW (). В приведенном выше коде есть небольшая ошибка: «while» (strA [jA] == zeroA) jA ++; & quot; и "while (strB [jB] == zeroB) jB ++; & quot; должен быть «while» (jA & lt; strA.Length & amp; strA [jA] == zeroA) jA ++; & quot; и "while (jB & lt; strB.Length & amp; strB [jB] == zeroB) jB ++; & quot ;. В противном случае строки, содержащие только нули, будут вызывать исключение. – kuroki 8 December 2015 в 05:27

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

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

10
ответ дан Jonathan Gilbert 20 August 2018 в 18:46
поделиться

Ответ Matthews Horsleys - это самый быстрый способ, который не изменяет поведение в зависимости от того, в какой версии окон работает ваша программа. Тем не менее, это может быть еще быстрее, создав регулярное выражение один раз и используя 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 - довольно быстро!

13
ответ дан Michael Parker 20 August 2018 в 18:46
поделиться

Мое решение:

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
19
ответ дан mpen 20 August 2018 в 18:46
поделиться
  • 1
    Этот код в конечном итоге находится в codeproject.com/KB/recipes/NaturalComparer.aspx (который не ориентирован на LINQ). – mhenry1384 31 August 2010 в 22:19
  • 2
    В блоге сообщение Юстина Джонса ( codeproject.com/KB/string/NaturalSortComparer.aspx ) для IComparer, а не Паскаля Ганайе. – James McCormack 1 September 2010 в 10:59
  • 3
    Ницца, работает так, как ожидалось. – Natxo 17 October 2012 в 15:12
  • 4
    Незначительное замечание, это решение игнорирует пробелы, которые не совпадают с окнами, и не так хорош, как код Мэтью Хорсли ниже. Таким образом, вы можете получить строку 'string01' 'string 01' 'string 02' 'string02' например (которая выглядит уродливой). Если вы удалите разделение пробелов, он упорядочивает строки назад, т. Е. Строка «string01» предшествует «строка 01», что может быть или не быть приемлемым. – Michael Parker 7 March 2014 в 19:43
  • 5
    Это работало для адресов, то есть «1 Smith Rd», «10 Smith Rd», «2 Smith Rd» и т. Д. - Сортировано естественно. Да! Хороший! – ppumkin 12 August 2015 в 15:18
  • 6
    Кстати, я заметил (и комментарии на этой связанной странице также, похоже, указывают), что аргумент Type & lt; T & gt; совершенно не нужно. – alias65536 2 June 2016 в 19:20
  • 7
    Мне это нравится. Это легко понять и не требует Linq. – RobinHood70 2 July 2016 в 20:31

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

List<String> alphaStrings = new List<string>() { "10","2","3","4","50","11","100","a12","b12" };
alphaStrings.OrderBy(g => new Tuple<int, string>(g.ToCharArray().All(char.IsDigit)? int.Parse(g) : int.MaxValue, g)).Dump();
// Order Now: ["2","3","4","10","11","50","100","a12","b12"]
0
ответ дан mshsayem 20 August 2018 в 18:46
поделиться

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

Сначала этот метод расширения:

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"

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

4
ответ дан Picsonald 20 August 2018 в 18:46
поделиться

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

  • StringComparer.CurrentCulture.WithNaturalSort() или
  • StringComparer.OrdinalIgnoreCase.WithNaturalSort() .

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

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


Я также опубликовал ее как tiny пакет NuGet , поэтому вы можете просто сделать:

Install-Package NaturalSort.Extension

Код, содержащий комментарии к документации XML и набор тестов тестов , доступен в репозиторий NaturalSort.Extension 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;
        }
    }
}
58
ответ дан Tom Pažourek 20 August 2018 в 18:46
поделиться
  • 1
    +1 Мало того, что это самый короткий, это самый быстрый из всех, что я видел. за исключением принятого ответа, но я не могу использовать его из-за машинных зависимостей. Он собрал более 4 миллионов значений за 35 секунд. – Gene S 4 October 2012 в 21:58
  • 2
    Это красиво и невозможно читать. Я полагаю, что преимущества Linq будут означать (по крайней мере) лучшие средние и лучшие результаты, поэтому я думаю, что я собираюсь пойти с ним. Несмотря на отсутствие ясности. Большое спасибо @Matthew Horsley – Ian Grainger 16 January 2014 в 13:29
  • 3
    Это очень хорошо, но есть ошибка для определенных десятичных чисел, мой пример сортировал k8.11 vs k8.2. Чтобы исправить это, я применил следующее regex: \ d + ([\.,] \ D)? – devzero 10 March 2016 в 11:26
  • 4
    Вы также должны учитывать длину второй группы (десятичная точка + десятичные знаки), когда вы вставляете этот код в m.Value.PadLeft (max, '0') – devzero 10 March 2016 в 13:52
  • 5
    Извините, сжато, но производительность ужасная. Принимая более секунды, чтобы сортировать короткие короткие строки сравнительно долгое время. – markmnl 1 December 2016 в 01:40

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

Селектор поддерживает типичную типизацию, позволяющую назначать любого делегата, элементы в исходной коллекции мутируются. Это означает, что он использует 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);
    }
1
ответ дан Vorspire 20 August 2018 в 18:46
поделиться
20
ответ дан mpen 31 October 2018 в 14:28
поделиться
0
ответ дан Oliver 31 October 2018 в 14:28
поделиться
61
ответ дан Tom Pažourek 31 October 2018 в 14:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: