Wie sortiert man ein Wörterbuch nach Wert?

796

Ich muss oft ein Wörterbuch, bestehend aus Schlüsseln und Werten, nach Wert sortieren. Zum Beispiel habe ich einen Hash von Wörtern und entsprechenden Frequenzen, die ich nach Häufigkeit ordnen möchte.

Es gibt einen SortedListWert für einen einzelnen Wert (z. B. Frequenz), den ich dem Wort wieder zuordnen möchte.

SortedDictionary ordnet nach Schlüssel, nicht nach Wert. Einige greifen auf eine benutzerdefinierte Klasse zurück , aber gibt es einen saubereren Weg?

Kalid
quelle
1
Abgesehen davon, dass Sie nur das Wörterbuch sortieren (wie in der akzeptierten Antwort), können Sie auch ein Wörterbuch erstellen IComparer, das den Trick ausführt (wahr, dass es einen zu vergleichenden Schlüssel akzeptiert, aber mit einem Schlüssel können Sie einen Wert erhalten). ;-)
BrainSlugs83

Antworten:

520

Verwenden:

using System.Linq.Enumerable;
...
List<KeyValuePair<string, string>> myList = aDictionary.ToList();

myList.Sort(
    delegate(KeyValuePair<string, string> pair1,
    KeyValuePair<string, string> pair2)
    {
        return pair1.Value.CompareTo(pair2.Value);
    }
);

Da Sie auf .NET 2.0 oder höher abzielen, können Sie dies in Lambda-Syntax vereinfachen - es ist äquivalent, aber kürzer. Wenn Sie auf .NET 2.0 abzielen, können Sie diese Syntax nur verwenden, wenn Sie den Compiler aus Visual Studio 2008 (oder höher) verwenden.

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Leon Bambrick
quelle
26
Ich habe diese Lösung verwendet (Danke!), War aber eine Minute lang verwirrt, bis ich Michael Stums Beitrag (und seinen Code-Ausschnitt von John Timney) las und feststellte, dass myList ein sekundäres Objekt ist, eine Liste von KeyValuePairs, die aus dem Wörterbuch erstellt wird. und dann sortiert.
Robin Bennett
113
es ist ein Liner - Sie brauchen keine Zahnspange. es kann umgeschrieben werden alsmyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa
25
Um absteigend zu sortieren, wechseln Sie das x und das y im Vergleich: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Ich denke, es ist erwähnenswert, dass dies Linq für die ToList-Erweiterungsmethode erfordert.
Ben
17
Ihr seid gespannt darauf, dies zu komplizieren - ein Wörterbuch implementiert bereits IEnumerable, so dass Sie eine sortierte Liste wie diese erhalten können:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

Verwenden Sie LINQ:

Dictionary<string, int> myDict = new Dictionary<string, int>();
myDict.Add("one", 1);
myDict.Add("four", 4);
myDict.Add("two", 2);
myDict.Add("three", 3);

var sortedDict = from entry in myDict orderby entry.Value ascending select entry;

Dies würde auch eine große Flexibilität ermöglichen, da Sie die Top 10, 20 10% usw. auswählen können. Wenn Sie Ihren Worthäufigkeitsindex für verwenden type-ahead, können Sie auch eine StartsWithKlausel einschließen .

Caryden
quelle
15
Wie kann ich sortedDict wieder in ein Dictionary <string, int> ändern? Gepostete
Kache
1
Leider funktioniert dies auf VS2005 aufgrund des dortigen .net Framework 2.0 (kein LINQ) nicht. Es ist gut, auch die Antwort von Bambrick zu haben.
Smalcat
22
Ich bin nicht sicher, ob es immer funktioniert, da das Durchlaufen des Wörterbuchs nicht garantiert, dass KeyValuePairs in der Reihenfolge "gezogen" werden, in der sie eingefügt wurden. Ergo spielt es keine Rolle, ob Sie orderby in LINQ verwenden, da Dictionary die Reihenfolge der eingefügten Elemente ändern kann. Es funktioniert normalerweise wie erwartet, aber es gibt KEINE GARANTIE, insbesondere für große Wörterbücher.
Bozydar Sobczak
16
Rückgabetyp sollte IEnumerable<KeyValuePair<TKey, TValue>>oder sein OrderedDictionary<TKey, TValue>. Oder man sollte SortedDictionaryvon Anfang an ein verwenden. Für eine Ebene gibt Dictionarydie MSDN eindeutig an: "Die Reihenfolge, in der die Artikel zurückgegeben werden, ist undefiniert." Es scheint, dass @ rythos42s neueste Bearbeitung schuld ist. :)
Boris B.
16
Bitte ignorieren Sie alle Vorschläge von .ToDictionary- Standardwörterbücher garantieren keine Sortierreihenfolge
AlexFoxGill
254
var ordered = dict.OrderBy(x => x.Value);
Sean
quelle
6
Ich bin mir nicht sicher, warum diese Lösung nicht beliebter ist - vielleicht weil sie .NET 3.5 erfordert?
Contango
33
Dies ist eine gute Lösung, sollte jedoch direkt vor dem endenden Semikolon stehen: .ToDictionary (pair => pair.Key, pair => pair.Value);
TheJerm
3
@theJerm durch Zurücksetzen der sortierten Elemente in ein Wörterbuch ist die Reihenfolge dann garantiert? Es könnte heute funktionieren, ist aber nicht garantiert.
Nawfal
1
Mit dem 4.5-Framework wurde nur überprüft, ob kein Zurück zum Wörterbuch erforderlich ist.
Jagd
12
Es sollte keine Besetzung eines Wörterbuchs geben, da Wörterbücher nicht bestellt werden. Es gibt keine Garantie dafür, dass die KeyValuePairs in der gewünschten Reihenfolge bleiben.
David DeMar
165

Wenn wir uns umschauen und einige C # 3.0-Funktionen verwenden, können wir dies tun:

foreach (KeyValuePair<string,int> item in keywordCounts.OrderBy(key=> key.Value))
{ 
    // do something with item.Key and item.Value
}

Dies ist die sauberste Art, die ich gesehen habe, und ähnelt der Ruby-Methode zum Umgang mit Hashes.

Kalid
quelle
Ich habe versucht, ein Wörterbuch zu sortieren, während ich die KeyValuePairs zu einer ComboBox hinzugefügt habe ... das hat großartig funktioniert! Vielen Dank!
Jason Down
6
Vergessen Sie nicht, den System.Linq-Namespace hinzuzufügen, wenn Sie diese Syntax verwenden.
M. Dudley
4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- Nur eine kleine Ergänzung zu Ihrer Antwort :) Danke übrigens :)
Andrius Naruševičius
7
@ AndriusNaruševičius: Wenn Sie die resultierenden Elemente wieder in ein Wörterbuch aufnehmen, wird die Reihenfolge zerstört, da nicht garantiert wird, dass Wörterbücher auf eine bestimmte Weise bestellt werden .
ODER Mapper
Das war praktisch. Wie kann es umgekehrt werden, um in die andere Richtung zu gehen?
Dan Hastings
158

Sie können ein Wörterbuch nach Wert sortieren und in sich selbst speichern (sodass die Werte in der angegebenen Reihenfolge angezeigt werden, wenn Sie darüber nachdenken):

dict = dict.OrderBy(x => x.Value).ToDictionary(x => x.Key, x => x.Value);

Sicher, es ist vielleicht nicht richtig, aber es funktioniert.

Matt Frear
quelle
8
Sie können OrderByDescending auch verwenden, wenn Sie in eine absteigende Liste sortieren möchten.
Mendokusai
Arbeitete für mich, obwohl ich es leicht ändern musste in: Dictionary <Schlüssel, Wert> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh
17
Dieses "Arbeiten" ist nicht garantiert. Es ist ein Implementierungsdetail. Es muss nicht zu anderen Zeiten funktionieren. Falsche Antwort, herabgestimmt.
Nawfal
4
Es wird NICHT garantiert, dass die Wörterbuchausgabe eine bestimmte Sortierreihenfolge aufweist.
Roger Willcocks
5
Ich wäre sehr besorgt, dies im Produktionscode zu sehen. Es ist nicht garantiert und kann jederzeit geändert werden. Nicht dass ich vor pragmatischen Lösungen zurückscheue, es zeigt nur einen Mangel an Verständnis für die Datenstruktur imo.
Jamespconnor
61

Auf einer hohen Ebene haben Sie keine andere Wahl, als durch das gesamte Wörterbuch zu gehen und sich jeden Wert anzusehen.

Vielleicht hilft das: http://bytes.com/forum/thread563638.html Kopieren / Einfügen von John Timney:

Dictionary<string, string> s = new Dictionary<string, string>();
s.Add("1", "a Item");
s.Add("2", "c Item");
s.Add("3", "b Item");

List<KeyValuePair<string, string>> myList = new List<KeyValuePair<string, string>>(s);
myList.Sort(
    delegate(KeyValuePair<string, string> firstPair,
    KeyValuePair<string, string> nextPair)
    {
        return firstPair.Value.CompareTo(nextPair.Value);
    }
);
Michael Stum
quelle
3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Art
2
Perfekte Nicht-Linq-Lösung. Es überrascht mich immer wieder, wie sehr Menschen das Bedürfnis haben, Linq zu verwenden, auch wenn es absolut nicht erforderlich ist, das Problem zu lösen. Ich glaube, Sie können mit C # 3 die Sortierung auch vereinfachen, indem Sie nur ein Lambda verwenden: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

Sie würden sowieso nie in der Lage sein, ein Wörterbuch zu sortieren. Sie sind eigentlich nicht bestellt. Die Garantien für ein Wörterbuch bestehen darin, dass die Schlüssel- und Wertsammlungen iterierbar sind und Werte per Index oder Schlüssel abgerufen werden können, es gibt jedoch keine Garantie für eine bestimmte Reihenfolge. Daher müssten Sie das Name-Wert-Paar in eine Liste aufnehmen.

Roger Willcocks
quelle
2
Ein sortiertes Wörterbuch könnte jedoch eine Liste von Schlüssel-Wert-Paaren ergeben.
rekursiv
1
@recursive Jedes Wörterbuch sollte das ergeben. Interessant zu bemerken, dass meine Antwort, die korrekt, aber unvollständig ist (hätte tun können, was die besseren Beispiele getan haben), unter einer ungültigen Antwort abgestimmt wird, die zu Ausnahmen bei doppelten Werten im ursprünglichen Wörterbuch führen würde (Schlüssel sind eindeutig, Werte sind nicht garantiert zu sein)
Roger Willcocks
5
Dies ist die Antwort, da das Wörterbuch nicht sortierbar ist. Es hasht Tasten und Sie können eine extrem schnelle Suchoperation ausführen.
Paulius Zaliaduonis
@ NetMage Ja. Aber der andere Teil des Problems ist, dass sie nach Wert geordnet werden wollten. Und das können Sie nur, indem Sie Schlüssel und Wert tauschen. Und Wert ist nicht unbedingt eindeutig, aber Schlüssel muss sein.
Roger Willcocks
Ja, aber ich denke, Ihre Antwort ist aufgrund der darin enthaltenen absoluten Aussagen falsch.
NetMage
21

Sie sortieren keine Einträge im Wörterbuch. Die Wörterbuchklasse in .NET ist als Hashtabelle implementiert - diese Datenstruktur kann per Definition nicht sortiert werden.

Wenn Sie in der Lage sein müssen, Ihre Sammlung (nach Schlüssel) zu durchlaufen, müssen Sie SortedDictionary verwenden, das als binärer Suchbaum implementiert ist.

In Ihrem Fall ist die Quellstruktur jedoch irrelevant, da sie nach einem anderen Feld sortiert ist. Sie müssten es weiterhin nach Häufigkeit sortieren und in eine neue Sammlung einfügen, die nach dem entsprechenden Feld (Häufigkeit) sortiert ist. In dieser Sammlung sind die Frequenzen Schlüssel und Wörter Werte. Da viele Wörter dieselbe Häufigkeit haben können (und Sie sie als Schlüssel verwenden werden), können Sie weder Dictionary noch SortedDictionary verwenden (sie erfordern eindeutige Schlüssel). Dadurch erhalten Sie eine SortedList.

Ich verstehe nicht, warum Sie darauf bestehen, einen Link zum Originalelement in Ihrem Haupt- / Erstwörterbuch beizubehalten.

Wenn die Objekte in Ihrer Sammlung eine komplexere Struktur hätten (mehr Felder) und Sie in der Lage sein müssten, mithilfe mehrerer verschiedener Felder als Schlüssel effizient auf sie zuzugreifen / sie zu sortieren, benötigen Sie wahrscheinlich eine benutzerdefinierte Datenstruktur, die aus dem Hauptspeicher besteht unterstützt das Einfügen und Entfernen von O (1) (LinkedList) und verschiedene Indexierungsstrukturen - Dictionaries / SortedDictionaries / SortedLists. Diese Indizes verwenden eines der Felder aus Ihrer komplexen Klasse als Schlüssel und einen Zeiger / Verweis auf den LinkedListNode in der LinkedList als Wert.

Sie müssten Einfügungen und Entfernungen koordinieren, um Ihre Indizes mit der Hauptsammlung (LinkedList) synchron zu halten, und Entfernungen wären meiner Meinung nach ziemlich teuer. Dies ähnelt der Funktionsweise von Datenbankindizes - sie eignen sich hervorragend für Suchvorgänge, werden jedoch zu einer Belastung, wenn Sie viele Einfügungen und Löschungen durchführen müssen.

All dies ist nur gerechtfertigt, wenn Sie eine nachschlagende Verarbeitung durchführen. Wenn Sie sie nur einmal nach Häufigkeit sortiert ausgeben müssen, können Sie einfach eine Liste von (anonymen) Tupeln erstellen:

var dict = new SortedDictionary<string, int>();
// ToDo: populate dict

var output = dict.OrderBy(e => e.Value).Select(e => new {frequency = e.Value, word = e.Key}).ToList();

foreach (var entry in output)
{
    Console.WriteLine("frequency:{0}, word: {1}",entry.frequency,entry.word);
}
Zar Shardan
quelle
15
Dictionary<string, string> dic= new Dictionary<string, string>();
var ordered = dic.OrderBy(x => x.Value);
return ordered.ToDictionary(t => t.Key, t => t.Value);
mrfazolka
quelle
12

Oder zum Spaß könnten Sie einige LINQ-Erweiterungsgüte verwenden:

var dictionary = new Dictionary<string, int> { { "c", 3 }, { "a", 1 }, { "b", 2 } };
dictionary.OrderBy(x => x.Value)
  .ForEach(x => Console.WriteLine("{0}={1}", x.Key,x.Value));
Mythos
quelle
10

Sortieren einer SortedDictionaryListe zum Binden in ein ListViewSteuerelement mithilfe von VB.NET:

Dim MyDictionary As SortedDictionary(Of String, MyDictionaryEntry)

MyDictionaryListView.ItemsSource = MyDictionary.Values.OrderByDescending(Function(entry) entry.MyValue)

Public Class MyDictionaryEntry ' Need Property for GridViewColumn DisplayMemberBinding
    Public Property MyString As String
    Public Property MyValue As Integer
End Class

XAML:

<ListView Name="MyDictionaryListView">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Path=MyString}" Header="MyStringColumnName"></GridViewColumn>
            <GridViewColumn DisplayMemberBinding="{Binding Path=MyValue}" Header="MyValueColumnName"></GridViewColumn>
         </GridView>
    </ListView.View>
</ListView>
BSalita
quelle
7

Der einfachste Weg, ein sortiertes Wörterbuch zu erhalten, ist die Verwendung der integrierten SortedDictionaryKlasse:

//Sorts sections according to the key value stored on "sections" unsorted dictionary, which is passed as a constructor argument
System.Collections.Generic.SortedDictionary<int, string> sortedSections = null;
if (sections != null)
{
    sortedSections = new SortedDictionary<int, string>(sections);
}

sortedSections will enthält die sortierte Version von sections

Alex Ruiz
quelle
7
Wie Sie in Ihrem Kommentar erwähnt haben, SortedDictionarysortieren Sie nach Schlüsseln. Das OP möchte nach Wert sortieren. SortedDictionaryhilft in diesem Fall nicht.
Marty Neal
Nun ... Wenn er / sie (Sie) kann, setzen Sie einfach die Werte als Schlüssel. Ich habe die Operationen zeitlich festgelegt und sorteddictionary()immer mindestens 1 Mikrosekunde gewonnen, und es ist viel einfacher zu verwalten (da der Aufwand für die Rückkonvertierung in etwas, mit dem man leicht interagieren und es ähnlich wie bei einem Wörterbuch verwalten kann, 0 beträgt (es ist bereits a sorteddictionary)).
mbrownnyc
2
@mbrownnyc - nein, dies erfordert die Annahme oder Voraussetzung, dass die WERTE eindeutig sind, was nicht garantiert ist.
Roger Willcocks
6

Die anderen Antworten sind gut, wenn Sie nur eine "temporäre" Liste nach Wert sortieren möchten. Wenn Sie jedoch möchten, dass ein nach Keydiesem sortiertes Wörterbuch automatisch mit einem anderen nach sortierten Wörterbuch synchronisiert wird Value, können Sie die Bijection<K1, K2>Klasse verwenden .

Bijection<K1, K2> Mit dieser Option können Sie die Sammlung mit zwei vorhandenen Wörterbüchern initialisieren. Wenn Sie also möchten, dass eines davon unsortiert und das andere sortiert wird, können Sie Ihre Bijektion mit Code wie erstellen

var dict = new Bijection<Key, Value>(new Dictionary<Key,Value>(), 
                               new SortedDictionary<Value,Key>());

Sie können dictwie jedes normale Wörterbuch (das es implementiert IDictionary<K, V>) verwenden und dann aufrufen dict.Inverse, um das "inverse" Wörterbuch zu erhalten, das nach sortiert istValue .

Bijection<K1, K2>ist Teil von Loyc.Collections.dll , aber wenn Sie möchten, können Sie den Quellcode einfach in Ihr eigenes Projekt kopieren .

Hinweis : Falls es mehrere Schlüssel mit demselben Wert gibt, können Sie diese nicht verwenden Bijection, aber Sie können manuell zwischen einem normalen Dictionary<Key,Value>und einem synchronisieren BMultiMap<Value,Key>.

Qwertie
quelle
Ähnlich wie http://stackoverflow.com/questions/268321 , kann jedoch jedes Wörterbuch durch SortedDictionary ersetzen. Obwohl die Antworten keine doppelten Werte unterstützen (nimmt 1 zu 1 an).
Crokusek
3

Angenommen, wir haben ein Wörterbuch als

   Dictionary<int, int> dict = new Dictionary<int, int>();
   dict.Add(21,1041);
   dict.Add(213, 1021);
   dict.Add(45, 1081);
   dict.Add(54, 1091);
   dict.Add(3425, 1061);
   sict.Add(768, 1011);

1) Sie können verwenden temporary dictionary to store values as:

        Dictionary<int, int> dctTemp = new Dictionary<int, int>();

        foreach (KeyValuePair<int, int> pair in dict.OrderBy(key => key.Value))
        {
            dctTemp .Add(pair.Key, pair.Value);
        }
Akshay Kapoor
quelle
3

Tatsächlich haben Wörterbücher in C # keine sort () -Methoden, da Sie mehr an der Sortierung nach Werten interessiert sind. Sie können keine Werte abrufen, bis Sie ihnen den Schlüssel geben. Kurz gesagt, Sie müssen sie mithilfe von LINQs Order By durchlaufen.

var items = new Dictionary<string, int>();
items.Add("cat", 0);
items.Add("dog", 20);
items.Add("bear", 100);
items.Add("lion", 50);

// Call OrderBy method here on each item and provide them the ids.
foreach (var item in items.OrderBy(k => k.Key))
{
    Console.WriteLine(item);// items are in sorted order
}

Sie können einen Trick machen,

var sortedDictByOrder = items.OrderBy(v => v.Value);

oder

var sortedKeys = from pair in dictName
            orderby pair.Value ascending
            select pair;

Es hängt auch davon ab, welche Art von Werten Sie speichern,
ob es sich um einzelne (wie Zeichenfolge, Int) oder mehrere (wie Liste, Array, benutzerdefinierte Klasse) handelt.
Wenn Sie einzelne Werte erstellen können, wenden Sie sort an.
Wenn eine benutzerdefinierte Klasse vorhanden ist, muss diese Klasse IComparable implementieren
ClassName: IComparable<ClassName>und überschreiben, compareTo(ClassName c) da sie schneller als LINQ und objektorientierter sind.

Ashish Kamble
quelle
0

Erforderlicher Namespace: using System.Linq;

Dictionary<string, int> counts = new Dictionary<string, int>();
counts.Add("one", 1);
counts.Add("four", 4);
counts.Add("two", 2);
counts.Add("three", 3);

Bestellung nach absteigend:

foreach (KeyValuePair<string, int> kvp in counts.OrderByDescending(key => key.Value))
{
// some processing logic for each item if you want.
}

Bestellung von Asc:

foreach (KeyValuePair<string, int> kvp in counts.OrderBy(key => key.Value))
{
// some processing logic for each item if you want.
}
Jaydeep Shil
quelle
-2

Sie können das Wörterbuch nach Wert sortieren und das Ergebnis im Wörterbuch mit dem folgenden Code abrufen:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
Pawan Kumar
quelle
8
Wenn Sie die sortierten Elemente wieder in ein Wörterbuch aufnehmen, wird nicht mehr garantiert, dass sie sortiert werden, wenn Sie das neue Wörterbuch auflisten.
Marty Neal
2
Und warum fügen Sie diese Antwort hinzu, wenn diese bereits beantwortet wurde?
Nawfal
-2

Wenn Sie über ein Wörterbuch verfügen, können Sie diese direkt nach Werten sortieren, indem Sie einen einzigen Liner verwenden:

var x = (from c in dict orderby c.Value.Order ascending select c).ToDictionary(c => c.Key, c=>c.Value);
Aggaton
quelle