Как я могу использовать C # для числовой сортировки значений? - PullRequest
10 голосов
/ 08 декабря 2010

У меня есть строка, которая содержит числа, разделенные точками. Когда я сортирую, это выглядит так, поскольку это строка: (порядок символов ASCII)

3.9.5.2.1.1
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4

и т.д.

Я хочу отсортировать так: (по порядку номеров)

3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
...
3.9.5.2.1.9
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12

Я знаю, что могу:

  1. Используйте функцию разделения, чтобы получить отдельные номера
  2. Поместить значения в объект
  3. Сортировка объекта

Я предпочитаю избегать всей этой работы, если она дублирует существующую функциональность. Это метод в .net Framework, который уже делает это?

Ответы [ 9 ]

4 голосов
/ 08 декабря 2010

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

Идея состоит в том, чтобы получить первое число в обеих строках и сравнить эти числа.Если они совпадают, переходите к следующему номеру.Если нет, у нас есть победитель.Если один, если эти числа вообще не являются числами, проведите сравнение строк части, которая еще не сравнивалась.

Было бы легко сделать компаратор полностью совместимым с естественным порядком сортировки, изменивспособ определения следующего числа.

Посмотрите на это .. только что нашел этот вопрос .

Сравнение:

class StringNumberComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        int compareResult;
        int xIndex = 0, yIndex = 0;
        int xIndexLast = 0, yIndexLast = 0;
        int xNumber, yNumber;
        int xLength = x.Length;
        int yLength = y.Length;

        do
        {
            bool xHasNextNumber = TryGetNextNumber(x, ref xIndex, out xNumber);
            bool yHasNextNumber = TryGetNextNumber(y, ref yIndex, out yNumber);

            if (!(xHasNextNumber && yHasNextNumber))
            {
                // At least one the strings has either no more number or contains non-numeric chars
                // In this case do a string comparison of that last part
                return x.Substring(xIndexLast).CompareTo(y.Substring(yIndexLast));
            }

            xIndexLast = xIndex;
            yIndexLast = yIndex;

            compareResult = xNumber.CompareTo(yNumber);
        }
        while (compareResult == 0
            && xIndex < xLength
            && yIndex < yLength);

        return compareResult;
    }

    private bool TryGetNextNumber(string text, ref int startIndex, out int number)
    {
        number = 0;

        int pos = text.IndexOf('.', startIndex);
        if (pos < 0) pos = text.Length;

        if (!int.TryParse(text.Substring(startIndex, pos - startIndex), out number))
            return false;

        startIndex = pos + 1;

        return true;
    }
}

Использование:

public static void Main()
{
    var comparer = new StringNumberComparer();

    List<string> testStrings = new List<string>{
        "3.9.5.2.1.1",
        "3.9.5.2.1.10",
        "3.9.5.2.1.11",
        "3.9.test2",
        "3.9.test",
        "3.9.5.2.1.12",
        "3.9.5.2.1.2",
        "blabla",
        "....",
        "3.9.5.2.1.3",
        "3.9.5.2.1.4"};

    testStrings.Sort(comparer);

    DumpArray(testStrings);

    Console.Read();
}

private static void DumpArray(List<string> values)
{
    foreach (string value in values)
    {
        Console.WriteLine(value);
    }
}

Выход:

....
3.9.5.2.1.1
3.9.5.2.1.2
3.9.5.2.1.3
3.9.5.2.1.4
3.9.5.2.1.10
3.9.5.2.1.11
3.9.5.2.1.12
3.9.test
3.9.test2
blabla
3 голосов
/ 08 декабря 2010

Нет, я не верю, что в рамках есть что-то, что делает это автоматически. Вы можете написать свою собственную реализацию IComparer<string>, которая не не делает никакого разделения, но вместо этого выполняет итерацию по обеим строкам, сравнивая только столько, сколько требуется (т.е. анализируя только первое число каждой, затем продолжая, если необходимо и т. д.) но это было бы довольно рискованно, я подозреваю. Также необходимо сделать предположения о том, как «1.2.3.4.5» сравнивать, например, с «1.3» (т. Е. Где значения содержат различное число чисел).

2 голосов
/ 08 декабря 2010

То, что вы ищете, - это естественный порядок сортировки, и Джефф Этвуд написал об этом и имеет ссылки на реализации на разных языках ..NET Framework не содержит реализацию.

2 голосов
/ 08 декабря 2010

Так как сравнение, которое вы хотите выполнить для строк, отличается от того, как строки обычно сравниваются в .Net, вам придется использовать специальный компаратор строк

 class MyStringComparer : IComparer<string>
        {
            public int Compare(string x, string y)
            {
                // your comparison logic
                // split the string using '.' separator
                // parse each string item in split array into an int
                // compare parsed integers from left to right
            }
        }

Тогда вы можете использовать компаратор в таких методах, как OrderBy и Sort

var sorted = lst.OrderBy(s => s, new MyStringComparer());

lst.Sort(new MyStringComparer());

Это даст вам желаемый результат. Если нет, то просто настройте компаратор.

0 голосов
/ 17 августа 2012

Вы можете использовать потрясающий AlphanumComparator Алфавитный алгоритм естественной сортировки от David Koelle.

Код:

OrderBy(o => o.MyString, new AlphanumComparator())

Если вы собираетесь использовать C #версия изменить его на:

AlphanumComparator : IComparer<string>

и

public int Compare(string x, string y)
0 голосов
/ 08 декабря 2010

Разделите каждую строку на «.», Переберите компоненты и сравните их численно.

В этом коде также предполагается, что количество компонентов является значительным (строка «1.1.1» будет больше, чем «2.1». Это можно отрегулировать, изменив первый оператор if в методе Compare, указанном ниже).

    int Compare(string a, string b)
    {
        string[] aParts = a.Split('.');
        string[] bParts = b.Split('.');

        /// if A has more components than B, it must be larger.
        if (aParts.Length != bParts.Length)
            return (aParts.Length > bParts.Length) ? 1 : -1;

        int result = 0;
        /// iterate through each numerical component

        for (int i = 0; i < aParts.Length; i++)
            if ( (result = int.Parse(aParts[i]).CompareTo(int.Parse(bParts[i]))) !=0 )
                return result;

        /// all components are equal.
        return 0;
    }



    public string[] sort()
    {
        /// initialize test data
        string l = "3.9.5.2.1.1\n"
        + "3.9.5.2.1.10\n"
        + "3.9.5.2.1.11\n"
        + "3.9.5.2.1.12\n"
        + "3.9.5.2.1.2\n"
        + "3.9.5.2.1.3\n"
        + "3.9.5.2.1.4\n";

        /// split the large string into lines
        string[] arr = l.Split(new char[] { '\n' },StringSplitOptions.RemoveEmptyEntries);
        /// create a list from the array
        List<string> strings = new List<string>(arr);
        /// sort using our custom sort routine
        strings.Sort(Compare);
        /// concatenate the list back to an array.
        return strings.ToArray();
    }
0 голосов
/ 08 декабря 2010

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

Попробуйте:

List<string> myList = GetNumberStrings();

myList.Select(s=>s.Split('.')).ToArray().
   .Sort((a,b)=>RecursiveCompare(a,b))
   .Select(a=>a.Aggregate(new StringBuilder(),
      (s,sb)=>sb.Append(s).Append(".")).Remove(sb.Length-1, 1).ToString())
   .ToList();

...

public int RecursiveCompare(string[] a, string[] b)
{
    return RecursiveCompare(a,b,0)
}

public int RecursiveCompare(string[] a, string[] b, int index)
{
    return index == a.Length || index == b.Length 
        ? 0 
        : a[index] < b[index] 
            ? -1 
            : a[index] > b[index] 
                ? 1 
                : RecursiveCompare(a,b, index++);
}

Не самый компактный, но он должен работатьи вы могли бы использовать Y-комбинатор, чтобы сделать сравнение лямбда.

0 голосов
/ 08 декабря 2010

В дополнение к реализации вашего собственного IComparer, как упоминает Джон, если вы вызываете ToList () для своего массива, вы можете вызвать метод .Sort () и передать параметр функции, который сравнивает два значения, как показано здесь: http://msdn.microsoft.com/en-us/library/w56d4y5z.aspx

0 голосов
/ 08 декабря 2010

Возможно ли для вас заполнить поля одинаковой длины спереди с помощью 0?Если это так, то вы можете просто использовать прямую лексикографическую сортировку по строкам.Иначе, нет такого метода, встроенного в платформу, которая делает это автоматически.Вам придется реализовать свой собственный IComparer<string>, если заполнение не поддерживается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...