2009-04-28 4 views
23

Ich habe ein Problem damit, wie die List Sort-Methode das Sortieren behandelt. Gegeben das folgende Element:Warum enthält die Liste <T> .Sort-Methode gleiche IComparable <T> Elemente?

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

    public int CompareTo(Element other) 
    { 
     return Priority.CompareTo(other.Priority); 
    } 
} 

Wenn ich versuche, es auf diese Weise zu sortieren:

List<Element> elements = new List<Element>() 
          { 
           new Element() 
            { 
             Priority = 1, 
             Description = "First" 
            }, 
           new Element() 
            { 
             Priority = 1, 
             Description = "Second" 
            }, 
           new Element() 
            { 
             Priority = 2, 
             Description = "Third" 
            } 
          }; 
elements.Sort(); 

Dann wird das erste Element ist das zuvor zweite Element "Second". Oder, mit anderen Worten, diese Behauptung nicht:

Assert.AreEqual("First", elements[0].Description); 

Warum ist Neuordnungs .NET meine Liste, wenn die Elemente im Wesentlichen gleich sind? Ich möchte, dass es nur die Liste neu anordnet, wenn der Vergleich einen Wert ungleich null zurückgibt.

+0

Feature-Anforderung für eine stabile Sortiermethode https://github.com/dotnet/corefx/issues/4696 –

Antwort

37

Aus der Dokumentation des List.Sort() -Methode von MSDN:

Diese Methode verwendet Array.Sort, die den QuickSort-Algorithmus verwendet. Diese Implementierung führt eine instabile Sortierung durch; Das heißt, wenn zwei Elemente gleich sind, wird ihre Reihenfolge möglicherweise nicht beibehalten. Im Gegensatz dazu behält eine stabile Sortierung die Reihenfolge der Elemente bei, die gleich sind.

Hier ist der Link: http://msdn.microsoft.com/en-us/library/b0zbh7b6.aspx

Im Wesentlichen wird die Art, wie entworfen Durchführung und dokumentiert.

+0

Entschuldigung, dass du deine Zeit damit verschwendet hast. Ich war so konzentriert auf die vergleichbare Implementierung, dass ich nicht dachte, die Sort-Dokumentation selbst zu betrachten. –

+4

Nein, ich habe Ihre Frage geupdated, weil die Sortierung zwar das tut, was sie tun soll, aber ich denke, es ist eine offensichtlich genug Frage, die Sie auf Stackoverflow haben sollten, obwohl sie in der MSDN-Dokumentation enthalten ist. Ich verstehe deinen Ausgangspunkt; Es ist nicht unbedingt sinnvoll, dass die Standardsortierung instabil ist. obwohl es dokumentiert ist, denke ich, dass genug Leute den gleichen Fehler machen werden, wenn sie Fragen dazu haben. –

3

Sie sagten es, wie man Dinge vergleicht und es tat. Sie sollten sich nicht auf die interne Implementierung von Sort in Ihrer Anwendung verlassen. Deshalb können Sie CompareTo überschreiben. Wenn Sie einen sekundären Sortierparameter (in diesem Fall "Beschreibung") haben möchten, codieren Sie ihn in Ihr CompareTo. Sich darauf zu verlassen, wie Sort gerade funktioniert, ist eine großartige Möglichkeit, einen Fehler zu programmieren, der sehr schwer zu finden ist.

Sie könnten einen stabilen Quicksort für .NET finden oder verwenden Sie eine merge sort (die bereits stabil ist).

+0

Ich stimme nicht zu; Die Sortierung funktioniert genau wie angegeben. Es stellt sich nur so heraus, dass das, was gesagt wird, anders ist als das, was er will; aber das ist ein Problem, mehr nicht lesen Dokumentation als alles andere. –

+0

Ja, tatsächlich ist es! Mein Sortieralgorithmus wäre "halten Sie es in Ordnung, es sei denn die Priorität ist anders". Ich möchte die Liste nicht meinen Elementen zugänglich machen, also wäre vielleicht ein Comparer die beste Option für das, was ich versuche. –

+0

@Michael: Ah! Sie wollen eine "stabile Sortierung", nicht nur eine Sorte. Eine normale Sortierung ist nicht garantiert stabil. –

1

Sie können dies beheben, indem Sie Ihrer Struktur einen "Indexwert" hinzufügen und diesen in der CompareTo-Methode einschließen, wenn Priority.CompareTo 0 zurückgibt. Sie müssten dann den Wert "index" vor der Sortierung initialisieren.

Die CompareTo Methode würde wie folgt aussehen:

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

Dann statt elements.Sort() zu tun, würden Sie tun:

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

die anderen Antworten Siehe warum List.Sort() ist instabil. Wenn Sie eine stabile Sortierung benötigen und .NET 3.5 verwenden, versuchen Sie Enumerable.OrderBy() (LINQ).

0

Wenn Sie wollten auf zwei Feldern statt einer sortieren Sie dies tun könnte:

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); 
     } 
    } 
} 

Offensichtlich ist dies die Voraussetzung eines stabilen Suchalgorithmus nicht erfüllt; Es erfüllt jedoch Ihre Behauptung und ermöglicht die Kontrolle Ihrer Elementordnung im Falle der Gleichheit.Hier

7

ist eine Erweiterung Methode SortStable() für 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 der Testmethode, wenn Sie rufen Sort() -Methode statt SortStable(), Sie kann sehen, dass der Test fehlschlagen würde.

+2

Es kann nur ** OrderBy (x => x) ** anstelle von ** OrderBy (x => x, neuer ComparableComparer ()) ** mit den gleichen Ergebnissen sein. – ogggre

1

In einigen Anwendungen, wenn eine Liste von Elementen nach einem bestimmten Kriterium sortiert wird, ist es nicht notwendig, die ursprüngliche Reihenfolge der Elemente beizubehalten, die gleichwertig sind. In anderen Anwendungen ist es notwendig. Sortiermethoden, die die Anordnung von Elementen mit übereinstimmenden Schlüsseln ("stabile Sortierungen") beibehalten, sind im Allgemeinen entweder viel langsamer als solche, die dies nicht tun ("instabile Sortierungen"), oder sie benötigen eine erhebliche Menge temporären Speicher (und sind immer noch etwas langsamer Die erste "Standard-Bibliothek" -Sortierroutine, die verbreitet wurde, war wahrscheinlich die qsort()-Funktion, die in der Standard-C-Bibliothek enthalten ist. Diese Bibliothek wurde häufig verwendet, um Listen zu sortieren, die relativ zu der Gesamtmenge an verfügbarem Speicher groß waren waren viel weniger nützlich, wenn es eine Menge an temporärer Speicherung proportional zur Anzahl der Elemente in der zu sortierenden Array benötigt hätte

Sortiermethoden, die unter Frameworks wie Java oder .net verwendet werden könnte praktisch viel nutzen mehr temporärer Speicher als in einem C qsor akzeptabel wäre t() Routine. Eine temporäre Speicheranforderung, die der Größe des zu sortierenden Arrays entspricht, würde in den meisten Fällen kein besonderes Problem darstellen. Da es für Bibliotheken jedoch üblich ist, eine Quicksort-Implementierung zu liefern, scheint dies das Muster zu sein, dem .net folgt.

Verwandte Themen