Comment triez-vous un dictionnaire par valeur?

796

Je dois souvent trier un dictionnaire, composé de clés et de valeurs, par valeur. Par exemple, j'ai un hachage de mots et de fréquences respectives, que je veux classer par fréquence.

Il y en a un SortedListqui est bon pour une seule valeur (disons la fréquence), que je veux mettre en correspondance avec le mot.

SortedDictionary ordonne par clé, pas par valeur. Certains ont recours à une classe personnalisée , mais existe-t-il une méthode plus propre?

Kalid
la source
1
En plus de simplement trier le dictionnaire (comme dans la réponse acceptée), vous pouvez également créer un IComparerqui fait l'affaire (c'est vrai qu'il accepte une clé à comparer, mais avec une clé, vous pouvez obtenir une valeur). ;-)
BrainSlugs83

Réponses:

520

Utilisation:

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

Comme vous ciblez .NET 2.0 ou supérieur, vous pouvez simplifier cela en syntaxe lambda - c'est équivalent, mais plus court. Si vous ciblez .NET 2.0, vous ne pouvez utiliser cette syntaxe que si vous utilisez le compilateur de Visual Studio 2008 (ou supérieur).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Leon Bambrick
la source
26
J'ai utilisé cette solution (merci!) Mais j'ai été confus pendant une minute jusqu'à ce que je lise le post de Michael Stum (et son extrait de code de John Timney) et que je réalise que myList est un objet secondaire, une liste de KeyValuePairs, qui est créée à partir du dictionnaire, puis triés.
Robin Bennett
113
c'est une doublure - Vous n'avez pas besoin d'appareils orthopédiques. il peut être réécrit commemyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa
25
Pour trier par ordre décroissant, basculez le x et le y dans la comparaison: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Je pense que cela vaut la peine de noter que cela nécessite Linq pour la méthode d'extension ToList.
Ben
17
Les gars, vous ne voulez pas compliquer cela - un dictionnaire implémente déjà IEnumerable, vous pouvez donc obtenir une liste triée comme ceci:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

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

Cela permettrait également une grande flexibilité dans la mesure où vous pouvez sélectionner le top 10, 20 10%, etc. Ou si vous utilisez votre indice de fréquence de mot pour type-ahead, vous pouvez également inclure une StartsWithclause.

caryden
la source
15
Comment puis-je reconvertir sortedDict en un dictionnaire <chaîne, int>? Posté une nouvelle question SO ici: stackoverflow.com/questions/3066182/…
Kache
1
Malheureusement, cela ne fonctionne pas sur VS2005 à cause du framework .net 2.0 (pas de LINQ). C'est bien d'avoir aussi la réponse de Bambrick.
Smalcat
22
Je ne sais pas si cela fonctionne toujours, car l'itération sur le dictionnaire ne garantit pas que les paires de valeurs clés sont "extraites" dans le même ordre qu'elles ont été insérées. Ergo, peu importe si vous utilisez orderby dans LINQ car Dictionary peut changer l'ordre des éléments insérés. Cela fonctionne généralement comme prévu, mais il n'y a AUCUNE GARANTIE, en particulier pour les grands dictionnaires.
Bozydar Sobczak
16
Le type de retour doit être IEnumerable<KeyValuePair<TKey, TValue>>ou an OrderedDictionary<TKey, TValue>. Ou on devrait utiliser un SortedDictionarydès le début. Pour une plaine, Dictionaryle MSDN indique clairement "L'ordre dans lequel les articles sont retournés n'est pas défini.". Il semble que le dernier montage de @ rythos42 soit à blâmer. :)
Boris B.
16
Veuillez ignorer toutes les suggestions de .ToDictionary- les dictionnaires standard ne garantissent pas un ordre de tri
AlexFoxGill
254
var ordered = dict.OrderBy(x => x.Value);
sean
la source
6
Je ne sais pas pourquoi cette solution n'est pas plus populaire - peut-être parce qu'elle nécessite .NET 3.5?
Contango
33
C'est une bonne solution, mais elle devrait avoir ceci juste avant le point-virgule final: .ToDictionary (pair => pair.Key, pair => pair.Value);
2012
3
@theJerm en remettant les éléments triés dans un dictionnaire est-ce que l'ordre est garanti alors? Cela pourrait fonctionner aujourd'hui, mais ce n'est pas garanti.
nawfal
1
En utilisant le framework 4.5, vient de vérifier qu'il ne nécessite pas de conversion en dictionnaire.
Jagd
12
Il ne doit pas y avoir de conversion vers un dictionnaire car les dictionnaires ne sont pas commandés. Il n'y a aucune garantie que les paires de valeurs clés resteront dans l'ordre que vous souhaitez.
David DeMar
165

En regardant autour de nous et en utilisant certaines fonctionnalités C # 3.0, nous pouvons le faire:

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

C'est la façon la plus propre que j'ai vue et elle est similaire à la manière Ruby de gérer les hachages.

Kalid
la source
J'essayais de trier un dictionnaire en ajoutant les KeyValuePairs à un ComboBox ... cela a très bien fonctionné! Merci!
Jason Down
6
N'oubliez pas d'ajouter l'espace de noms System.Linq lorsque vous utilisez cette syntaxe.
M. Dudley
4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- juste un petit ajout à votre réponse :) Merci, btw :)
Andrius Naruševičius
7
@ AndriusNaruševičius: Si vous ajoutez les éléments résultants dans un dictionnaire, vous détruirez l'ordre, car les dictionnaires ne sont pas garantis d'être commandés d'une manière particulière .
OR Mapper du
C'était pratique. Comment peut-il être inversé pour aller dans l'autre sens?
Dan Hastings
158

Vous pouvez trier un dictionnaire par valeur et le sauvegarder sur lui-même (de sorte que lorsque vous l'avez consulté, les valeurs sortent dans l'ordre):

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

Bien sûr, ce n'est peut-être pas correct, mais cela fonctionne.

Matt Frear
la source
8
Vous pouvez également utiliser OrderByDescending si vous souhaitez trier dans une liste décroissante.
Mendokusai
A fonctionné pour moi, même si j'ai dû le changer légèrement en: Dictionary <key, value> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh
17
Ce "fonctionnement" n'est pas garanti. C'est un détail d'implémentation. Cela n'a pas besoin de fonctionner à d'autres moments. Mauvaise réponse, sous-voté.
nawfal
4
La sortie du dictionnaire n'est PAS garantie d'avoir un ordre de tri particulier.
Roger Willcocks
5
Je serais très inquiet de voir cela dans le code de production. Il n'est pas garanti et pourrait changer à tout moment. Non pas que je répugne aux solutions pragmatiques, cela montre simplement un manque de compréhension de la structure de données imo.
jamespconnor
61

À un niveau élevé, vous n'avez pas d'autre choix que de parcourir l'ensemble du dictionnaire et d'examiner chaque valeur.

Peut-être que cela aide: http://bytes.com/forum/thread563638.html Copier / Coller de 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
la source
3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Art
2
Solution parfaite non Linq. Cela ne cesse de m'étonner de la façon dont les gens ressentent le besoin d'utiliser Linq même s'il n'est absolument pas nécessaire pour résoudre le problème. Avec C # 3, je pense que vous pouvez également simplifier le tri pour simplement utiliser un lambda: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

De toute façon, vous ne pourrez jamais trier un dictionnaire. Ils ne sont pas réellement commandés. Les garanties pour un dictionnaire sont que les collections de clés et de valeurs sont itérables, et les valeurs peuvent être récupérées par index ou clé, mais il n'y a aucune garantie d'un ordre particulier. Par conséquent, vous auriez besoin d'obtenir la paire valeur-nom dans une liste.

Roger Willcocks
la source
2
Un dictionnaire trié pourrait cependant fournir une liste de paires clé-valeur.
récursif
1
@recursive Tout dictionnaire devrait donner cela. Il est intéressant de noter que ma réponse, qui est correcte, mais incomplète (aurait pu faire ce que les meilleurs exemples ont fait) est votée en dessous d'une réponse non valide qui entraînerait des exceptions sur les valeurs en double dans le dictionnaire d'origine (les clés sont uniques, les valeurs ne sont pas garanties être)
Roger Willcocks
5
C'est la réponse à la question, car Dictionary n'est pas triable. Il hache les touches et vous pouvez effectuer une opération de recherche extrêmement rapide dessus.
Paulius Zaliaduonis
@NetMage Oui. Mais l'autre partie du problème est qu'ils voulaient être commandés par Value. Et vous ne pouvez le faire qu'en échangeant Clé et Valeur. Et la valeur n'est pas nécessairement unique, mais la clé doit l'être.
Roger Willcocks
Oui, mais je pense que votre réponse est incorrecte en raison des déclarations absolues qu'elle contient.
NetMage
21

Vous ne triez pas les entrées du dictionnaire. La classe de dictionnaire dans .NET est implémentée comme une table de hachage - cette structure de données n'est pas triable par définition.

Si vous devez pouvoir parcourir votre collection (par clé) - vous devez utiliser SortedDictionary, qui est implémenté comme un arbre de recherche binaire.

Dans votre cas, cependant, la structure source n'est pas pertinente, car elle est triée par un champ différent. Vous auriez encore besoin de le trier par fréquence et de le mettre dans une nouvelle collection triée par le champ pertinent (fréquence). Ainsi, dans cette collection, les fréquences sont des clés et les mots sont des valeurs. Étant donné que de nombreux mots peuvent avoir la même fréquence (et que vous allez l'utiliser comme clé), vous ne pouvez pas utiliser ni Dictionary ni SortedDictionary (ils nécessitent des clés uniques). Cela vous laisse avec une SortedList.

Je ne comprends pas pourquoi vous insistez pour maintenir un lien vers l'élément d'origine dans votre dictionnaire principal / premier.

Si les objets de votre collection avaient une structure plus complexe (plus de champs) et que vous deviez pouvoir y accéder / trier efficacement en utilisant plusieurs champs différents comme clés - Vous auriez probablement besoin d'une structure de données personnalisée qui consisterait en le stockage principal qui prend en charge l'insertion et la suppression de O (1) (LinkedList) et plusieurs structures d'indexation - Dictionnaires / SortedDictionaries / SortedLists. Ces index utiliseraient l'un des champs de votre classe complexe comme clé et un pointeur / référence vers LinkedListNode dans LinkedList comme valeur.

Vous auriez besoin de coordonner les insertions et les suppressions pour garder vos index synchronisés avec la collection principale (LinkedList) et les suppressions seraient assez chères, je pense. Ceci est similaire au fonctionnement des index de base de données - ils sont fantastiques pour les recherches mais ils deviennent un fardeau lorsque vous devez effectuer de nombreuses insertions et suppressions.

Tout ce qui précède n'est justifié que si vous allez effectuer un traitement lourd de recherche. Si vous n'avez besoin de les sortir qu'une fois triés par fréquence, vous pouvez simplement produire une liste de tuples (anonymes):

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
la source
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
la source
12

Ou pour le plaisir, vous pouvez utiliser certains avantages de l'extension 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));
mythz
la source
10

Tri d'une SortedDictionaryliste à lier dans un ListViewcontrôle à l'aide de 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
la source
7

La façon la plus simple d'obtenir un dictionnaire trié est d'utiliser la SortedDictionaryclasse intégrée :

//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 testament contient la version triée de sections

Alex Ruiz
la source
7
Comme vous le mentionnez dans votre commentaire, SortedDictionarytrie par clés. L'OP veut trier par valeur. SortedDictionaryn'aide pas dans ce cas.
Marty Neal du
Eh bien ... S'il (vous) le pouvez, définissez simplement les valeurs comme clés. J'ai chronométré les opérations et j'ai sorteddictionary()toujours gagné par au moins 1 microseconde, et c'est beaucoup plus facile à gérer (car la surcharge de la reconversion en quelque chose d'interagir et de gérer facilement de la même manière qu'un dictionnaire est 0 (c'est déjà un sorteddictionary)).
mbrownnyc
2
@mbrownnyc - non, faire cela nécessite l'hypothèse ou la condition préalable que les VALEURS sont uniques, ce qui n'est pas garanti.
Roger Willcocks
6

Les autres réponses sont bonnes, si tout ce que vous voulez, c'est d'avoir une liste "temporaire" triée par valeur. Cependant, si vous souhaitez avoir un dictionnaire trié par Keyqui se synchronise automatiquement avec un autre dictionnaire trié par Value, vous pouvez utiliser la Bijection<K1, K2>classe .

Bijection<K1, K2> vous permet d'initialiser la collection avec deux dictionnaires existants, donc si vous voulez que l'un d'eux ne soit pas trié et que l'autre soit trié, vous pouvez créer votre bijection avec du code comme

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

Vous pouvez utiliser dictcomme n'importe quel dictionnaire normal (il implémente IDictionary<K, V>), puis appeler dict.Inversepour obtenir le dictionnaire "inverse" qui est trié par Value.

Bijection<K1, K2>fait partie de Loyc.Collections.dll , mais si vous le souhaitez, vous pouvez simplement copier le code source dans votre propre projet.

Remarque : dans le cas où plusieurs clés ont la même valeur, vous ne pouvez pas les utiliser Bijection, mais vous pouvez synchroniser manuellement entre un ordinaire Dictionary<Key,Value>et un BMultiMap<Value,Key>.

Qwertie
la source
Similaire à http://stackoverflow.com/questions/268321 mais peut remplacer chaque dictionnaire par SortedDictionary. Bien que les réponses ne semblent pas prendre en charge les valeurs en double (suppose 1 à 1).
crokusek
3

Supposons que nous ayons un dictionnaire comme

   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) vous pouvez utiliser 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
la source
3

En fait, en C #, les dictionnaires ont des méthodes sort (), car vous êtes plus intéressé par le tri par valeurs, vous ne pouvez pas obtenir de valeurs jusqu'à ce que vous leur fournissiez la clé, bref, vous devez les parcourir, en utilisant Order By de LINQ,

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
}

vous pouvez faire un tour,

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

ou

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


Cela dépend également du type de valeurs que vous stockez, est-il unique (comme chaîne, entier) ou multiple (comme Liste, Tableau, classe définie par l'utilisateur),
si unique, vous pouvez en faire une liste puis appliquer le tri.
si la classe est définie par l'utilisateur, cette classe doit implémenter IComparable
ClassName: IComparable<ClassName>et remplacer compareTo(ClassName c) car elles sont plus rapides que LINQ et plus orientées objet.

Ashish Kamble
la source
0

Espace de noms requis: 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);

Trier par desc:

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

Ordre par Asc:

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

Vous pouvez trier le dictionnaire par valeur et obtenir le résultat dans le dictionnaire en utilisant le code ci-dessous:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
pawan Kumar
la source
8
En replaçant les éléments triés dans un dictionnaire, il n'est plus garanti qu'ils soient triés lorsque vous énumérez le nouveau dictionnaire.
Marty Neal du
2
Et pourquoi ajoutez-vous cette réponse alors qu'elle est déjà répondue?
nawfal
-2

Étant donné que vous avez un dictionnaire, vous pouvez les trier directement sur les valeurs en utilisant ci-dessous une ligne:

var x = (from c in dict orderby c.Value.Order ascending select c).ToDictionary(c => c.Key, c=>c.Value);
aggaton
la source
4
Cette réponse est incorrecte, car le dictionnaire résultant n'est pas garanti d'être commandé.
OR Mapper du