Проблема естественной сортировки

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

D-016.0,
D-016.,
D-016.00,
D-016.000 001,
D-016.000 002,
D-016.000,
D-016.00 003,
D-016.00 002,
D-016. 001,
D-016. 002,
D-016.0 001,
D-016.00 001

который по какой-то очень странной причине возвращает коллекцию в порядке

D-016.00,
D-016.000,
D-016.0,
D-016.000 001,
D-016. 001,
D-016.0 001,
D-016.00 001,
D-016.00 002,
D-016.000 002,
D-016. 002,
D-016.00 003,
D-016.

Коллекция, которую я ожидаю увидеть, будет выглядеть так, как показал бы проводник Windows, что должно быть

D-016. 001,
D-016. 002,
D-016.,
D-016.000 001,
D-016.000 002,
D-016.000,
D-016.00 001,
D-016.00 002,
D-016.00 003,
D-016.00,
D-016.0 001,
D-016.0,

Следуя предложению Natural Sort Order in C#, опубликованному Майкл Книскерн, я попытался реализовать ответ, который использует shlwapi.dll, и это возвращает порядок, который я ожидал бы увидеть в соответствии с последней коллекцией, перечисленной выше. Однако этот подход работает только в Windows 7 и возвращает неравномерную коллекцию в Windows XP.

Я подозреваю, что файлы, указанные как D-016.00, D-016.0 и D-016.000, получат список нулей, объединенных при синтаксическом анализе в int, что приведет к этой ошибке. Однако я не могу точно понять, как именно это исправить (слишком новичок в этих интерфейсах сравнения). Может ли кто-нибудь предложить решение для этого?

Ниже приведен код, который я сейчас использую

public class NaturalSorter<T> : IComparer<string>, IDisposable
{
    private readonly bool ascending;
    private Dictionary<string, string[]> table = new Dictionary<string, string[]>();


    public NaturalSorter(bool inAscendingOrder = true)
    {
        ascending = inAscendingOrder;
    }

    #region IComparer Members


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

    #endregion

    #region IComparer 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 = 0;

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

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

        return ascending ? 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

    public void Dispose()
    {
        table.Clear();
        table = null;
    }
}

person Mo Patel    schedule 29.01.2013    source источник
comment
Естественный порядок сортировки и порядок сортировки проводника Windows не совпадают. Проводник Windows имеет особую обработку, поскольку он рассматривает часть после последней точки как расширение файла, если оно не начинается с пробела. Это приводит к другому порядку.   -  person Daniel Hilgarth    schedule 29.01.2013
comment
Вам не нужно использовать причудливые алгоритмы естественной сортировки, если ваши числа дополнены нулями, как здесь. Кроме того, я не могу определить критерии, по которым вы получили ожидаемый порядок сортировки. Я ожидал D-016., D-016. 001, D-016. 002, D-016.0, ...   -  person JosephHirn    schedule 29.01.2013


Ответы (1)


Это лучшее, что я мог придумать:

Замените разделение на:

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

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

И добавьте следующее в начало PartCompare()

private static int PartCompare(string left, string right)
{
     if (left.Length > right.Length) return -1;
     else if (left.Length < right.Length) return 1;

     int x, y;
     ...

Это производит:

D-016. 001,
D-016. 002,
D-016.000 001,
D-016.000 002,
D-016.000,
D-016.00 001,
D-016.00 002,
D-016.00 003,
D-016.00,
D-016.0 001,
D-016.0,
D-016.,  // The one out-of-order item.
person Bobson    schedule 29.01.2013