Jak sortujesz słownik według wartości?

796

Często muszę sortować słownik, składający się z kluczy i wartości, według wartości. Na przykład mam skrót słów i odpowiednie częstotliwości, które chcę uporządkować według częstotliwości.

Jest coś, SortedListco jest dobre dla pojedynczej wartości (powiedzmy częstotliwości), którą chcę odwzorować z powrotem na słowo.

SortedDictionary zamówienia według klucza, a nie wartości. Niektórzy uciekają się do klasy niestandardowej , ale czy istnieje czystszy sposób?

Kalid
źródło
1
Oprócz samego sortowania słownika (tak jak w zaakceptowanej odpowiedzi), możesz także stworzyć taki IComparer, który załatwi sprawę (prawda, że ​​akceptuje klucz do porównania, ale za pomocą klucza można uzyskać wartość). ;-)
BrainSlugs83

Odpowiedzi:

520

Posługiwać się:

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

Ponieważ celujesz w .NET 2.0 lub nowszy, możesz uprościć to w składni lambda - jest to równoważne, ale krótsze. Jeśli celujesz w .NET 2.0, możesz użyć tej składni tylko wtedy, gdy używasz kompilatora z Visual Studio 2008 (lub nowszego).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Leon Bambrick
źródło
26
Użyłem tego rozwiązania (Dzięki!), Ale byłem zdezorientowany przez minutę, dopóki nie przeczytałem posta Michaela Stuma (i jego fragmentu kodu od Johna Timneya) i zdałem sobie sprawę, że myList jest obiektem wtórnym, listą KeyValuePairs, która jest tworzona ze słownika, a następnie posortowane.
Robin Bennett
113
to jedna wkładka - nie potrzebujesz aparatów ortodontycznych. można go przepisać jakomyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa
25
Aby posortować malejąco, przełącz xi y w porównaniu: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Myślę, że warto zauważyć, że wymaga to Linq dla metody rozszerzenia ToList.
Ben
17
Wiesz, że to komplikujesz - słownik już implementuje IEnumerable, więc możesz uzyskać posortowaną listę w następujący sposób:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

Użyj 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;

Pozwoliłoby to również na dużą elastyczność, ponieważ możesz wybrać najwyższe 10, 20 10% itd. Lub jeśli używasz indeksu częstotliwości słów type-ahead, możesz również dołączyć StartsWithklauzulę.

Caryden
źródło
15
Jak mogę zmienić sortedDict z powrotem na słownik <string, int>?
Wysłałem
1
Niestety nie działa to na VS2005 z powodu .NET Framework 2.0 (brak LINQ). Dobrze jest mieć również odpowiedź Bambricka.
Smalcat
22
Nie jestem pewien, czy zawsze działa, ponieważ iteracja po słowniku nie gwarantuje, że KeyValuePairs są „wyciągane” w tej samej kolejności, w jakiej zostały wstawione. Ergo, nie ma znaczenia, czy używasz orderby w LINQ, ponieważ Słownik może zmieniać kolejność wstawianych elementów. Zwykle działa zgodnie z oczekiwaniami, ale NIE MA GWARANCJI, szczególnie w przypadku dużych słowników.
Bozydar Sobczak 27.01.12
16
Zwraca typ powinien być IEnumerable<KeyValuePair<TKey, TValue>>albo OrderedDictionary<TKey, TValue>. Lub należy użyć SortedDictionaryod samego początku. W przypadku zwykłego DictionaryMSDN wyraźnie stwierdza „Kolejność zwracania elementów jest niezdefiniowana”. Wygląda na to, że najnowsza edycja @ rythos42 jest winna. :)
Boris B.,
16
Proszę zignorować wszystkie sugestie dotyczące .ToDictionary- standardowe słowniki nie gwarantują sortowania
AlexFoxGill
254
var ordered = dict.OrderBy(x => x.Value);
Sean
źródło
6
Nie jestem pewien, dlaczego to rozwiązanie nie jest bardziej popularne - być może dlatego, że wymaga .NET 3.5?
Contango,
33
To dobre rozwiązanie, ale powinno mieć to tuż przed kończącym średnikiem: .ToDictionary (pair => pair.Key, pair => pair.Value);
TheJerm
3
@Jeżeli umieszczając posortowane pozycje z powrotem w słowniku, czy kolejność jest gwarantowana? To może działać dzisiaj, ale nie ma gwarancji.
nawfal
1
Za pomocą frameworka 4.5 właśnie zweryfikowałem, że nie wymaga on rzutowania z powrotem do słownika.
Jagd
12
Nie powinno się przesyłać ponownie do słownika, ponieważ słowniki nie są uporządkowane. Nie ma gwarancji, że KeyValuePairs pozostanie w żądanej kolejności.
David DeMar,
165

Rozglądając się i korzystając z niektórych funkcji C # 3.0, możemy to zrobić:

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

Jest to najczystszy sposób, jaki widziałem i jest podobny do Ruby'ego do obsługi skrótów.

Kalid
źródło
Próbowałem posortować słownik, dodając KeyValuePairs do ComboBox ... działało to świetnie! Dzięki!
Jason Down,
6
Nie zapomnij dodać przestrzeni nazw System.Linq podczas korzystania z tej składni.
M. Dudley,
4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- tylko mały dodatek do twojej odpowiedzi :) Dzięki, btw :)
Andrius Naruševičius
7
@ AndriusNaruševičius: Jeśli dodasz powstałe elementy z powrotem do słownika, zniszczysz porządek, ponieważ słowniki nie są gwarantowane w żaden szczególny sposób .
LUB Mapper
To było przydatne. Jak można odwrócić, aby przejść w drugą stronę?
Dan Hastings
158

Możesz posortować słownik według wartości i zapisać go z powrotem do siebie (aby po przekroczeniu go wartości pojawiały się w kolejności):

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

Jasne, może nie być poprawne, ale działa.

Matt Frear
źródło
8
Możesz także użyć OrderByDescending, jeśli chcesz posortować listę malejącą.
Mendokusai,
Pracował dla mnie, chociaż musiałem go nieco zmienić na: Słownik <klucz, wartość> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh
17
To „działające” nie jest gwarantowane. Jest to szczegół implementacji. To nie musi działać innym razem. Błędna odpowiedź, przegłosowana.
nawfal
4
Dane wyjściowe Słownika NIE są gwarantowane, że mają określoną kolejność sortowania.
Roger Willcocks
5
Byłbym bardzo zaniepokojony, widząc to w kodzie produkcyjnym. Nie jest to gwarantowane i może ulec zmianie w dowolnym momencie. Nie dlatego, że unikam pragmatycznych rozwiązań, to po prostu pokazuje brak zrozumienia imo struktury danych.
jamespconnor
61

Na wysokim poziomie nie masz innego wyjścia, jak przejść przez cały Słownik i spojrzeć na każdą wartość.

Może to pomaga: http://bytes.com/forum/thread563638.html Kopiowanie / wklejanie od Johna Timneya:

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
źródło
3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Art
2
Idealne rozwiązanie inne niż Linq. Nigdy nie przestaje mnie zadziwiać, jak ludzie czują potrzebę korzystania z Linq, nawet jeśli absolutnie nie jest to konieczne do rozwiązania problemu. Wydaje mi się, że w C # 3 możesz uprościć sortowanie, używając po prostu lambda: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

Zresztą nigdy nie będziesz w stanie posortować słownika. W rzeczywistości nie są zamówione. Gwarancje dla słownika polegają na tym, że kolekcje kluczy i wartości są iterowalne, a wartości można wyszukiwać według indeksu lub klucza, ale nie ma gwarancji na żadną konkretną kolejność. Dlatego musisz umieścić parę wartości nazwy na liście.

Roger Willcocks
źródło
2
Posortowany słownik może jednak dać listę par klucz-wartość.
rekurencyjny
1
@recursive Każdy słownik powinien to dać. Interesujące jest to, że moja odpowiedź, która jest poprawna, ale niekompletna (mogła zrobić to, co zrobiły lepsze przykłady), została przegłosowana poniżej niepoprawnej odpowiedzi, która spowodowałaby wyjątki od duplikatów wartości w oryginalnym słowniku (klucze są unikalne, wartości nie są gwarantowane być)
Roger Willcocks
5
To jest odpowiedź bes, ponieważ słownik nie jest sortowalny. Hashuje klawisze i możesz na nim wykonać niezwykle szybkie wyszukiwanie.
Paulius Zaliaduonis
@NetMage Tak. Ale drugą częścią problemu jest to, że chcieli uporządkować według wartości. I możesz to zrobić tylko poprzez zamianę Klucz i Wartość. A Wartość niekoniecznie jest wyjątkowa, ale Klucz musi być.
Roger Willcocks
Tak, ale myślę, że twoja odpowiedź jest nieprawidłowa z powodu bezwzględnych stwierdzeń.
NetMage
21

Nie sortujesz wpisów w Słowniku. Klasa słownika w .NET jest implementowana jako tablica mieszająca - tej struktury danych nie można z definicji sortować.

Jeśli chcesz mieć możliwość iteracji w swojej kolekcji (według klucza) - musisz użyć SortedDictionary, który jest zaimplementowany jako drzewo wyszukiwania binarnego.

W twoim przypadku jednak struktura źródłowa nie ma znaczenia, ponieważ jest posortowana według innego pola. Nadal będziesz musiał posortować dane według częstotliwości i umieścić je w nowej kolekcji posortowanej według odpowiedniego pola (częstotliwości). W tej kolekcji częstotliwości to klucze, a słowa to wartości. Ponieważ wiele słów może mieć tę samą częstotliwość (i użyjesz go jako klucza), nie możesz używać ani słownika, ani SortedDictionary (wymagają one unikatowych kluczy). To pozostawia Ci SortedList.

Nie rozumiem, dlaczego nalegasz na utrzymanie linku do oryginalnej pozycji w głównym / pierwszym słowniku.

Jeśli obiekty w Twojej kolekcji miały bardziej złożoną strukturę (więcej pól) i musiałeś mieć możliwość skutecznego dostępu / sortowania ich przy użyciu kilku różnych pól jako kluczy - prawdopodobnie potrzebowałbyś niestandardowej struktury danych, która składałaby się z głównej pamięci, która obsługuje wstawianie i usuwanie O (1) (LinkedList) oraz kilka struktur indeksujących - Dictionaries / SortedDictionaries / SortedLists. Indeksy te wykorzystałyby jedno z pól z twojej klasy złożonej jako klucz oraz wskaźnik / referencję do LinkedListNode w LinkedList jako wartość.

Trzeba koordynować wstawiania i usuwania, aby utrzymać synchronizację indeksów z kolekcją główną (LinkedList), a usuwanie byłoby dość drogie. Jest to podobne do działania indeksów baz danych - są fantastyczne do wyszukiwania, ale stają się obciążeniem, gdy trzeba wykonać wiele wstawień i usunięć.

Wszystko powyższe jest uzasadnione tylko wtedy, gdy zamierzasz przeprowadzić ciężkie przetwarzanie. Jeśli potrzebujesz je wyprowadzić tylko raz posortowane według częstotliwości, możesz po prostu utworzyć listę (anonimowych) krotek:

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
źródło
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
źródło
12

Lub dla zabawy możesz użyć jakiegoś rozszerzenia LINQ:

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));
mit
źródło
10

Sortowanie SortedDictionarylisty do powiązania z ListViewkontrolką za pomocą 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
źródło
7

Najłatwiejszym sposobem uzyskania posortowanego słownika jest użycie wbudowanej SortedDictionaryklasy:

//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 będzie zawierać posortowaną wersję sections

Alex Ruiz
źródło
7
Jak wspominasz w komentarzu, SortedDictionarysortuj według kluczy. OP chce sortować według wartości. SortedDictionarynie pomaga w tym przypadku.
Marty Neal,
Cóż ... Jeśli on / ona (ty) może, po prostu ustaw wartości jako klucze. Zmierzyłem czas operacji i sorteddictionary()zawsze wygrywałem o co najmniej 1 mikrosekundę, i jest to o wiele łatwiejsze do zarządzania (ponieważ narzut związany z przekształcaniem go z powrotem w coś, z czym łatwo można się współdziałać i którym zarządza się podobnie do słownika, wynosi 0 (to już jest a sorteddictionary)).
mbrownnyc
2
@mbrownnyc - nie, zrobienie tego wymaga założenia lub warunku, że WARTOŚCI są unikalne, co nie jest gwarantowane.
Roger Willcocks
6

Inne odpowiedzi są dobre, jeśli wszystko, czego chcesz, to mieć „tymczasową” listę posortowaną według wartości. Jeśli jednak chcesz posortować słownik, Keyktóry automatycznie synchronizuje się z innym sortowanym słownikiem Value, możesz użyć tej Bijection<K1, K2>klasy .

Bijection<K1, K2> pozwala na zainicjowanie kolekcji za pomocą dwóch istniejących słowników, więc jeśli chcesz, aby jeden z nich był nieposortowany, a drugi chcesz posortować, możesz utworzyć swój bijection za pomocą kodu podobnego do

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

Możesz użyć dictdowolnego zwykłego słownika (implementuje IDictionary<K, V>), a następnie wywołać, dict.Inverseaby uzyskać posortowany słownik „odwrotny” Value.

Bijection<K1, K2>jest częścią Loyc.Collections.dll , ale jeśli chcesz, możesz po prostu skopiować kod źródłowy do własnego projektu.

Uwaga : Jeśli istnieje wiele kluczy o tej samej wartości, nie możesz ich użyć Bijection, ale możesz ręcznie zsynchronizować pomiędzy zwykłym Dictionary<Key,Value>a a BMultiMap<Value,Key>.

Qwertie
źródło
Podobne do http://stackoverflow.com/questions/268321, ale można zastąpić każdy słownik SortedDictionary. Mimo że odpowiedzi nie obsługują zduplikowanych wartości (zakłada się od 1 do 1).
crokusek
3

Załóżmy, że mamy słownik jako

   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) możesz użyć 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
źródło
3

Właściwie w C #, słowniki nie mają metod sort (), ponieważ bardziej interesuje cię sortowanie według wartości, nie możesz uzyskać wartości, dopóki nie podasz im klucza, krótko mówiąc, musisz iterować za ich pomocą, używając LINQ's Sortuj według,

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
}

możesz zrobić jedną sztuczkę,

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

lub

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

zależy również od rodzaju przechowywanych wartości, czy
jest pojedynczy (jak łańcuch, int) czy wielokrotny (jak Lista, Tablica, klasa zdefiniowana przez użytkownika),
jeśli pojedynczy możesz zrobić listę, to zastosuj sort.
jeśli klasa zdefiniowana przez użytkownika, to klasa ta musi implementować IComparable
ClassName: IComparable<ClassName>i przesłonić, compareTo(ClassName c) ponieważ są one szybsze niż LINQ i bardziej obiektowe.

Ashish Kamble
źródło
0

Wymagana przestrzeń nazw: 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);

Sortuj według:

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

Sortuj według Asc:

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

Możesz posortować słownik według wartości i uzyskać wynik w słowniku, korzystając z poniższego kodu:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
pawan Kumar
źródło
8
Umieszczając posortowane elementy z powrotem w słowniku, nie ma już gwarancji, że zostaną posortowane podczas wyliczania nowego słownika.
Marty Neal,
2
I dlaczego dodajesz tę odpowiedź, gdy już jest na nią udzielona odpowiedź?
nawfal
-2

Biorąc pod uwagę, że masz słownik, możesz posortować je bezpośrednio według wartości, używając jednego linijki:

var x = (from c in dict orderby c.Value.Order ascending select c).ToDictionary(c => c.Key, c=>c.Value);
aggaton
źródło
4
Ta odpowiedź jest niepoprawna, ponieważ nie można zagwarantować
LUB Mapper