Come si ordina un dizionario per valore?


796

Spesso devo ordinare un dizionario, composto da chiavi e valori, per valore. Ad esempio, ho un hash di parole e rispettive frequenze, che voglio ordinare per frequenza.

C'è un SortedListvalore buono per un singolo valore (diciamo frequenza), che voglio mappare di nuovo sulla parola.

SortedDictionary ordina per chiave, non per valore. Alcuni ricorrono a una lezione personalizzata , ma esiste un modo più pulito?


1
Oltre a ordinare semplicemente il dizionario (come nella risposta accettata), potresti anche creare semplicemente un IComparertrucco (vero che accetta una chiave da confrontare, ma con una chiave puoi ottenere un valore). ;-)
BrainSlugs83

Risposte:


520

Uso:

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

Dato che stai prendendo di mira .NET 2.0 o versioni successive, puoi semplificarlo nella sintassi lambda: è equivalente, ma più breve. Se si utilizza .NET 2.0 come destinazione, è possibile utilizzare questa sintassi solo se si utilizza il compilatore di Visual Studio 2008 (o versione successiva).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));

26
Ho usato questa soluzione (grazie!) Ma sono rimasto confuso per un minuto fino a quando ho letto il post di Michael Stum (e il suo frammento di codice di John Timney) e ho capito che myList è un oggetto secondario, un elenco di KeyValuePairs, creato dal dizionario, e poi ordinati.
Robin Bennett,

113
è una fodera - Non hai bisogno di parentesi graffe. può essere riscritto comemyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa il

25
Per ordinare in modo discendente, cambiare xe y sul confronto: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo,

8
Penso che valga la pena notare che ciò richiede Linq per il metodo di estensione ToList.
Ben

17
Ragazzi, non state complicando questo aspetto - un dizionario è già implementato IEnumerable, quindi è possibile ottenere un elenco ordinato come questo:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83

523

Usa 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;

Ciò consentirebbe anche una grande flessibilità in quanto è possibile selezionare i primi 10, 20 10%, ecc. O se si utilizza l'indice di frequenza delle parole per type-ahead, è possibile includere anche la StartsWithclausola.


15
Come posso cambiare sortDict in un dizionario <string, int>? Pubblicato nuova domanda ecco: stackoverflow.com/questions/3066182/...
Kache

1
Purtroppo questo non funziona su VS2005 a causa di .net framework 2.0 lì (senza LINQ). È bello avere anche la risposta di Bambrick.
Smalcat,

22
Non sono sicuro che funzioni sempre perché l'iterazione sul dizionario non garantisce che KeyValuePairs venga "estratto" nello stesso ordine in cui sono stati inseriti. Ergo, non importa se usi orderby in LINQ perché Dictionary può cambiare l'ordine degli elementi inseriti. Di solito funziona come previsto ma NON c'è GARANZIA, specialmente per dizionari di grandi dimensioni.
Bozydar Sobczak,

16
Il tipo restituito dovrebbe essere IEnumerable<KeyValuePair<TKey, TValue>>o un OrderedDictionary<TKey, TValue>. Oppure si dovrebbe usare a SortedDictionarydall'inizio. Per un semplice DictionaryMSDN afferma chiaramente "L'ordine in cui gli articoli vengono restituiti non è definito.". Sembra che la colpa sia dell'ultima modifica di @rythos42. :)
Boris B.

16
Ignora tutti i suggerimenti di .ToDictionary- i dizionari standard non garantiscono un ordinamento
AlexFoxGill

254
var ordered = dict.OrderBy(x => x.Value);

6
Non sono sicuro del perché questa soluzione non sia più popolare, forse perché richiede .NET 3.5?
Contango,

33
Questa è una buona soluzione, ma dovrebbe avere questo diritto prima del punto e virgola finale: .ToDictionary (pair => pair.Key, pair => pair.Value);
theJerm

3
@theJerm rimettendo gli articoli ordinati in un dizionario, allora l'ordine è garantito? Potrebbe funzionare oggi, ma non è garantito.
nawfal,

1
Utilizzando il framework 4.5, ho appena verificato che non richiede un cast al dizionario.
Jagd,

12
Non ci dovrebbe essere un cast di nuovo in un dizionario perché i dizionari non sono ordinati. Non è garantito che KeyValuePairs rimanga nell'ordine desiderato.
David DeMar,

165

Guardandoci intorno e usando alcune funzionalità di C # 3.0 possiamo farlo:

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

Questo è il modo più pulito che ho visto ed è simile al modo in cui Ruby gestisce gli hash.


Stavo cercando di ordinare un dizionario mentre aggiungevo KeyValuePairs a un ComboBox ... questo ha funzionato alla grande! Grazie!
Jason Down,

6
Non dimenticare di aggiungere lo spazio dei nomi System.Linq quando si utilizza questa sintassi.
M. Dudley,

4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- solo una piccola aggiunta alla tua risposta :) Grazie, a proposito :)
Andrius Naruševičius

7
@ AndriusNaruševičius: se aggiungi nuovamente gli articoli risultanti in un dizionario, distruggerai l'ordine, poiché i dizionari non sono garantiti in alcun modo .
OR Mapper

Questo è stato utile. Come si può invertire per andare dall'altra parte?
Dan Hastings,

158

Puoi ordinare un Dizionario in base al valore e salvarlo di nuovo su se stesso (in modo che, quando lo prevedi, i valori escano in ordine):

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

Certo, potrebbe non essere corretto, ma funziona.


8
È inoltre possibile utilizzare OrderByDescending se si desidera ordinare in un elenco decrescente.
Mendokusai,

Ha funzionato per me, anche se ho dovuto cambiarlo leggermente in: Dizionario <chiave, valore> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh,

17
Questo "funzionamento" non è garantito. È un dettaglio di implementazione. Non è necessario che funzioni altre volte. Risposta errata, retrocessa.
nawfal,

4
Non è garantito che l'output del dizionario abbia un particolare ordinamento.
Roger Willcocks,

5
Sarei abbastanza preoccupato di vederlo nel codice di produzione. Non è garantito e potrebbe cambiare in qualsiasi momento. Non che mi allontani da soluzioni pragmatiche, mostra solo una mancanza di comprensione della struttura dei dati imo.
jamespconnor,

61

A un livello elevato, non hai altra scelta che attraversare l'intero dizionario e guardare ogni valore.

Forse questo aiuta: http://bytes.com/forum/thread563638.html Copia / Incolla da 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);
    }
);

3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Art

2
Perfetta soluzione non Linq. Non smette mai di stupirmi di come le persone sentano il bisogno di usare Linq anche quando non è assolutamente necessario per risolvere il problema. Con C # 3, credo che tu possa anche semplificare l'ordinamento per usare solo una lambda: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));

25

Non saresti mai in grado di ordinare un dizionario comunque. In realtà non sono ordinati. Le garanzie per un dizionario sono che le raccolte di chiavi e valori sono iterabili e che i valori possono essere recuperati per indice o chiave, ma non esiste alcuna garanzia per alcun ordine particolare. Quindi è necessario inserire la coppia valore-nome in un elenco.


2
Tuttavia, un dizionario ordinato potrebbe generare un elenco di coppie chiave-valore.
ricorsivo il

1
@recursive Qualsiasi dizionario dovrebbe produrlo. Interessante notare che la mia risposta, che è corretta, ma incompleta (avrebbe potuto fare quello che hanno fatto gli esempi migliori) è votata sotto una risposta non valida che comporterebbe eccezioni su valori duplicati nel dizionario originale (le chiavi sono uniche, i valori non sono garantiti essere)
Roger Willcocks,

5
Questa è la risposta di bes, perché Dictionary non è ordinabile. Ha le chiavi e puoi eseguire un'operazione di ricerca estremamente veloce su di essa.
Paulius Zaliaduonis,

@NetMage Sì. Ma l'altra parte del problema è che volevano ordinato per Valore. E puoi farlo solo scambiando Chiave e Valore. E il valore non è necessariamente unico, ma la chiave deve esserlo.
Roger Willcocks,

Sì, ma penso che la tua risposta sia errata a causa delle affermazioni assolute in essa contenute.
NetMage

21

Non ordinare le voci nel dizionario. La classe del dizionario in .NET è implementata come hashtable - questa struttura di dati non è ordinabile per definizione.

Se devi essere in grado di scorrere la tua raccolta (per chiave), devi utilizzare SortedDictionary, che viene implementato come albero di ricerca binario.

Nel tuo caso, tuttavia, la struttura di origine è irrilevante, poiché è ordinata per un campo diverso. Dovresti comunque ordinarlo per frequenza e inserirlo in una nuova raccolta ordinata per il campo pertinente (frequenza). Quindi in questa raccolta le frequenze sono chiavi e le parole sono valori. Poiché molte parole possono avere la stessa frequenza (e la userete come chiave) non è possibile utilizzare né Dictionary né SortedDictionary (richiedono chiavi univoche). Questo ti lascia con un SortedList.

Non capisco perché insisti a mantenere un collegamento all'elemento originale nel tuo dizionario principale / primo.

Se gli oggetti nella tua raccolta avessero una struttura più complessa (più campi) e tu dovessi essere in grado di accedervi / ordinarli in modo efficiente usando diversi campi come chiavi - Probabilmente avresti bisogno di una struttura di dati personalizzata che consisterebbe nella memoria principale che supporta l'inserimento e la rimozione di O (1) (LinkedList) e diverse strutture di indicizzazione - Dictionaries / SortedDictionaries / SortedLists. Questi indici utilizzerebbero uno dei campi della tua classe complessa come chiave e un puntatore / riferimento a LinkedListNode in LinkedList come valore.

Dovresti coordinare inserimenti e rimozioni per mantenere i tuoi indici sincronizzati con la raccolta principale (LinkedList) e le rimozioni sarebbero piuttosto costose, penso. Questo è simile al modo in cui funzionano gli indici del database: sono fantastici per le ricerche ma diventano un peso quando è necessario eseguire molte eliminazioni ed eliminazioni.

Tutto quanto sopra è giustificato solo se hai intenzione di eseguire qualche elaborazione pesante di ricerca. Se hai bisogno di emetterli solo una volta ordinati per frequenza, potresti semplicemente produrre un elenco di tuple (anonime):

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

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

12

O per divertimento potresti usare un po 'di estensione 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));

10

Ordinamento di un SortedDictionaryelenco da associare a un ListViewcontrollo tramite 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>

7

Il modo più semplice per ottenere un dizionario ordinato è utilizzare la SortedDictionaryclasse integrata :

//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 contiene la versione ordinata di sections


7
Come accennato nel tuo commento, SortedDictionaryordina per chiavi. L'OP vuole ordinare per valore. SortedDictionarynon aiuta in questo caso.
Marty Neal,

Bene ... Se lui / lei può, basta impostare i valori come chiavi. Ho cronometrato le operazioni e ho sorteddictionary()sempre vinto di almeno 1 microsecondo, ed è molto più facile da gestire (poiché il sovraccarico di convertirlo in qualcosa con cui è possibile interagire e gestire in modo simile a un dizionario è 0 (è già a sorteddictionary)).
mbrownnyc,

2
@mbrownnyc - no, per farlo è necessario presupporre o presupporre che i VALORI siano unici, il che non è garantito.
Roger Willcocks,

6

Le altre risposte sono buone, se tutto quello che vuoi è avere un elenco "temporaneo" ordinato per Valore. Tuttavia, se si vuole avere un dizionario ordinato secondo Keyche si sincronizza automaticamente con un altro dizionario che viene allineati secondo Value, è possibile utilizzare il Bijection<K1, K2>codice categoria .

Bijection<K1, K2> ti consente di inizializzare la raccolta con due dizionari esistenti, quindi se vuoi che uno di essi non sia ordinato e desideri che l'altro sia ordinato, puoi creare la tua biiezione con codice come

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

È possibile utilizzare dictcome qualsiasi altro dizionario normale (implementa IDictionary<K, V>) e quindi chiamare dict.Inverseper ottenere il dizionario "inverso" che viene ordinato per Value.

Bijection<K1, K2>fa parte di Loyc.Collections.dll , ma se lo desideri, puoi semplicemente copiare il codice sorgente nel tuo progetto.

Nota : nel caso in cui vi siano più chiavi con lo stesso valore, non è possibile utilizzare Bijection, ma è possibile sincronizzare manualmente tra un normale Dictionary<Key,Value>e un BMultiMap<Value,Key>.


Simile a http://stackoverflow.com/questions/268321 ma può sostituire ogni dizionario con SortedDictionary. Anche se le risposte sembrano non supportare valori duplicati (presuppone da 1 a 1).
crokusek,

3

Supponiamo di avere un dizionario come

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

3

In realtà in C #, i dizionari dint hanno metodi sort (), poiché sei più interessato all'ordinamento per valori, non puoi ottenere valori fino a quando non fornisci loro la chiave, in breve, devi iterarli attraverso, usando LINQ's Order By,

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
}

puoi fare un trucco,

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

o

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

dipende anche dal tipo di valori che stai memorizzando,
è singolo (come stringa, int) o multiplo (come Elenco, matrice, classe definita dall'utente),
se singolo puoi crearne un elenco e quindi applicare l'ordinamento.
se una classe definita dall'utente, quella classe deve implementare IComparable
ClassName: IComparable<ClassName>e sovrascrivere compareTo(ClassName c) poiché sono più veloci di LINQ e più orientati agli oggetti.


0

Spazio dei nomi richiesto: 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);

Ordina per descrizione:

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

Ordina per Asc:

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

-2

Puoi ordinare il Dizionario per valore e ottenere il risultato nel dizionario usando il codice seguente:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          

8
Rimettendo gli elementi ordinati in un dizionario, non è più garantito che vengano ordinati quando si enumera il nuovo dizionario.
Marty Neal,

2
E perché stai aggiungendo questa risposta quando questa ha già una risposta?
nawfal,

-2

Dato che hai un dizionario, puoi ordinarli direttamente sui valori usando sotto un liner:

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

4
Questa risposta non è corretta, poiché non è garantito
OR Mapper
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.