Why does List<T>.Sort method reorder equal IComparable<T> elements?

给你一囗甜甜゛ 提交于 2019-11-27 22:44:21

From the documentation of the List.Sort() method from MSDN:

This method uses Array.Sort, which uses the QuickSort algorithm. This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.

Here's the link: http://msdn.microsoft.com/en-us/library/b0zbh7b6.aspx

Essentially, the sort is performing as designed and documented.

Here is an extension method SortStable() for List<T> where T : IComparable<T>:

public static void SortStable<T>(this List<T> list) where T : IComparable<T>
{
    var listStableOrdered = list.OrderBy(x => x, new ComparableComparer<T>()).ToList();
    list.Clear();
    list.AddRange(listStableOrdered);
}

private class ComparableComparer<T> : IComparer<T> where T : IComparable<T>
{
    public int Compare(T x, T y)
    {
        return x.CompareTo(y);
    }
}

Test:

[Test]
public void SortStable()
{
    var list = new List<SortItem>
                    {
                        new SortItem{ SortOrder = 1, Name = "Name1"},
                        new SortItem{ SortOrder = 2, Name = "Name2"},
                        new SortItem{ SortOrder = 2, Name = "Name3"},
                    };

    list.SortStable();

    Assert.That(list.ElementAt(0).SortOrder, Is.EqualTo(1));
    Assert.That(list.ElementAt(0).Name, Is.EqualTo("Name1"));
    Assert.That(list.ElementAt(1).SortOrder, Is.EqualTo(2));
    Assert.That(list.ElementAt(1).Name, Is.EqualTo("Name2"));
    Assert.That(list.ElementAt(2).SortOrder, Is.EqualTo(2));
    Assert.That(list.ElementAt(2).Name, Is.EqualTo("Name3"));
}

private class SortItem : IComparable<SortItem>
{
    public int SortOrder { get; set; }
    public string Name { get; set; }

    public int CompareTo(SortItem other)
    {
        return SortOrder.CompareTo(other.SortOrder);
    }
}

In the test method, if you call Sort() method instead of SortStable(), you can see that the test would fail.

You told it how to compare things and it did. You should not rely on internal implementation of Sort in your application. That's why it let's you override CompareTo. If you want to have a secondary sort parameter ("description" in this case), code it into your CompareTo. Relying on how Sort just happens to work is a great way to code in a bug that is very difficult to find.

You could find a stable quicksort for .NET or use a merge sort (which is already stable).

See the other responses for why List.Sort() is unstable. If you need a stable sort and are using .NET 3.5, try Enumerable.OrderBy() (LINQ).

You can fix this by adding an "index value" to your structure, and including that in the CompareTo method when Priority.CompareTo returns 0. You would then need to initialize the "index" value before doing the sort.

The CompareTo method would look like this:

public int CompareTo(Element other)
{
    var ret = Priority.CompareTo(other.Priority);
    if (ret == 0)
    {
        ret = Comparer<int>.Default.Compare(Index, other.Index);
    }
    return ret;
}

Then instead of doing elements.Sort(), you would do:

for(int i = 0; i < elements.Count; ++i)
{
    elements[i].Index = i;
}
elements.Sort();

In some applications, when a list of items is sorted according to some criterion, preserving the original order of items which compare equal is unnecessary. In other applications, it is necessary. Sort methods which preserve the arrangement of items with matching keys (called "stable sorts" are generally either much slower than those which do not ("unstable sorts"), or else they require a significant amount of temporary storage (and are still somewhat slower). The first "standard library" sort routine to become widespread was probably the qsort() function included in the standard C library. That library would frequently have been used to sort lists that were large relative to the total amount of memory available. The library would have been much less useful if it had required an amount of temporary storage proportional to the number of items in the array to be sorted.

Sort methods that will be used under frameworks like Java or .net could practically make use of much more temporary storage than would have been acceptable in a C qsort() routine. A temporary memory requirement equal to the size of the array to be sorted would in most cases not pose any particular problem. Nonetheless, since it's been traditional for libraries to supply a Quicksort implementation, that seems to be the pattern followed by .net.

If you wanted to sort based on two fields instead of one you could do this:

class Element : IComparable<Element>
{
    public int Priority { get; set; }
    public string Description { get; set; }

    public int CompareTo(Element other)
    {
        if (Priority.CompareTo(other.Priority) == 0)
        {
            return Description.CompareTo(other.Description);
        } else {
            return Priority.CompareTo(other.Priority);
        }
    }
}

Obviously, this doesn't satisfy the requirement of a stable search algorithm; However, it does satisfy your assertion, and allows control of your element order in the event of equality.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!