¿Cómo se ordena un diccionario por valor?

796

A menudo tengo que ordenar un diccionario, que consta de claves y valores, por valor. Por ejemplo, tengo un hash de palabras y frecuencias respectivas, que quiero ordenar por frecuencia.

Hay una SortedListque es buena para un solo valor (por ejemplo, frecuencia), que quiero asignar de nuevo a la palabra.

Ordenes ordenadas por clave, no por valor. Algunos recurren a una clase personalizada , pero ¿hay una forma más limpia?

Kalid
fuente
1
Además de ordenar el diccionario (como en la respuesta aceptada), también podría crear uno IComparerque haga el truco (es cierto que acepta una clave para comparar, pero con una clave, puede obtener un valor). ;-)
BrainSlugs83

Respuestas:

520

Utilizar:

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

Como está apuntando a .NET 2.0 o superior, puede simplificar esto en sintaxis lambda: es equivalente, pero más corto. Si está apuntando a .NET 2.0, solo puede usar esta sintaxis si está usando el compilador de Visual Studio 2008 (o superior).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Leon Bambrick
fuente
26
Utilicé esta solución (¡Gracias!) Pero estuve confundido por un minuto hasta que leí la publicación de Michael Stum (y su fragmento de código de John Timney) y me di cuenta de que myList es un objeto secundario, una lista de KeyValuePairs, que se creó a partir del diccionario, y luego ordenado.
Robin Bennett
113
es un revestimiento: no necesita frenos. puede reescribirse comomyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa
25
Para ordenar descendente, cambie la xy la y en la comparación: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Creo que vale la pena señalar que esto requiere Linq para el método de extensión ToList.
Ben
17
Ustedes están muuuy complicados con esto: un diccionario ya se implementa IEnumerable, por lo que pueden obtener una lista ordenada como esta:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

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

Esto también permitiría una gran flexibilidad, ya que puede seleccionar los 10 mejores, 20 10%, etc. O si está utilizando su índice de frecuencia de palabras para type-ahead , también podría incluir una StartsWithcláusula.

Carryden
fuente
15
¿Cómo puedo cambiar sortedDict nuevamente en un Dictionary <string, int>? Nueva pregunta SO publicada aquí: stackoverflow.com/questions/3066182/…
Kache
1
Lamentablemente, esto no funciona en VS2005 debido a .NET Framework 2.0 allí (sin LINQ). Es bueno tener también la respuesta de Bambrick.
Smalcat
22
No estoy seguro de si siempre funciona porque iterar sobre el diccionario no garantiza que KeyValuePairs se "extraigan" en el mismo orden en que se insertaron. Ergo, no importa si usa orderby en LINQ porque Dictionary puede cambiar el orden de los elementos insertados. Por lo general, funciona como se esperaba, pero NO HAY GARANTÍA, especialmente para diccionarios grandes.
Bozydar Sobczak
16
El tipo de retorno debe ser IEnumerable<KeyValuePair<TKey, TValue>>o an OrderedDictionary<TKey, TValue>. O uno debería usar un SortedDictionarydesde el principio. Para un plano, Dictionaryel MSDN establece claramente "El orden en que se devuelven los elementos no está definido". Parece que la última edición de @ rythos42 es la culpable. :)
Boris B.
16
No tenga en cuenta todas las sugerencias de .ToDictionary- los diccionarios estándar no garantizan un orden de clasificación
AlexFoxGill
254
var ordered = dict.OrderBy(x => x.Value);
sean
fuente
66
No estoy seguro de por qué esta solución no es más popular, ¿tal vez porque requiere .NET 3.5?
Contango
33
Esta es una buena solución, pero debería tenerla justo antes del punto y coma final: .ToDictionary (pair => pair.Key, pair => pair.Value);
theJerm
3
@theJerm, al volver a colocar los elementos ordenados en un diccionario, ¿se garantiza el orden? Puede funcionar hoy, pero no está garantizado.
nawfal
1
Usando el marco 4.5, solo verifiqué que no requiere una conversión al diccionario.
Jagd
12
No debe volverse a emitir a un diccionario porque los diccionarios no están ordenados. No hay garantía de que los KeyValuePairs se mantengan en el orden que desee.
David DeMar
165

Mirando a nuestro alrededor y usando algunas características de C # 3.0 podemos hacer esto:

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

Esta es la forma más limpia que he visto y es similar a la forma Ruby de manejar hashes.

Kalid
fuente
Estaba tratando de ordenar un diccionario mientras agregaba KeyValuePairs a un ComboBox ... ¡esto funcionó muy bien! ¡Gracias!
Jason Down
66
No olvide agregar el espacio de nombres System.Linq cuando use esta sintaxis.
M. Dudley
44
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- solo una pequeña adición a tu respuesta :) Gracias, por cierto :)
Andrius Naruševičius
77
@ AndriusNaruševičius: si vuelve a agregar los elementos resultantes en un diccionario, destruirá el orden, ya que no se garantiza que los diccionarios se ordenen de ninguna manera en particular .
O Mapper el
Esto fue útil. ¿Cómo se puede invertir para ir hacia otro lado?
Dan Hastings
158

Puede ordenar un Diccionario por valor y guardarlo de nuevo en sí mismo (de modo que cuando lo lea por encima los valores salgan en orden):

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

Claro, puede que no sea correcto, pero funciona.

Matt Frear
fuente
8
También puede usar OrderByDescending si desea ordenar en una lista descendente.
Mendokusai
Me funcionó, aunque tuve que cambiarlo ligeramente a: Diccionario <clave, valor> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh
17
Este "trabajo" no está garantizado. Es un detalle de implementación. No necesita funcionar otras veces. Respuesta incorrecta, rechazada.
nawfal
44
NO se garantiza que la salida del diccionario tenga un orden de clasificación particular.
Roger Willcocks
55
Me preocuparía mucho ver esto en el código de producción. No está garantizado y podría cambiar en cualquier momento. No es que evite las soluciones pragmáticas, solo muestra una falta de comprensión de la estructura de datos de la OMI.
jamespconnor
61

En un nivel alto, no tiene otra opción que recorrer todo el Diccionario y observar cada valor.

Tal vez esto ayude: http://bytes.com/forum/thread563638.html Copiar / Pegar 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
fuente
3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Art
2
Solución perfecta que no es de Linq. Nunca deja de sorprenderme cómo la gente siente la necesidad de usar Linq, incluso cuando no es absolutamente necesario para resolver el problema. Con C # 3, creo que también puede simplificar la clasificación para usar solo una lambda: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

Nunca podrías ordenar un diccionario de todos modos. En realidad no están ordenados. Las garantías para un diccionario son que las colecciones de clave y valor son iterables, y los valores se pueden recuperar por índice o clave, pero no hay garantía de ningún orden en particular. Por lo tanto, necesitaría obtener el par de valor de nombre en una lista.

Roger Willcocks
fuente
2
Sin embargo, un diccionario ordenado podría generar una lista de pares clave-valor.
Recurrente el
1
@recursive Cualquier diccionario debería producir eso. Es interesante notar que mi respuesta, que es correcta, pero incompleta (podría haber hecho lo que hicieron los mejores ejemplos) se vota debajo de una respuesta no válida que daría lugar a excepciones en valores duplicados en el diccionario original (las claves son únicas, los valores no están garantizados to be)
Roger Willcocks
55
Esta es la mejor respuesta, porque el Diccionario no se puede ordenar. Hashes Keys y puede realizar una operación de búsqueda extremadamente rápida en él.
Paulius Zaliaduonis
@NetMage Sí. Pero la otra parte del problema es que querían ordenados por Value. Y solo puede hacer eso intercambiando Clave y Valor. Y el valor no es necesariamente único, pero Key debe serlo.
Roger Willcocks
Sí, pero creo que su respuesta es incorrecta debido a las declaraciones absolutas que contiene.
NetMage
21

No ordena entradas en el Diccionario. La clase de diccionario en .NET se implementa como una tabla hash: esta estructura de datos no se puede ordenar por definición.

Si necesita poder iterar sobre su colección (por clave), debe usar SortedDictionary, que se implementa como un árbol de búsqueda binario.

En su caso, sin embargo, la estructura fuente es irrelevante, ya que está ordenada por un campo diferente. Aún necesitaría ordenarlo por frecuencia y colocarlo en una nueva colección ordenada por el campo relevante (frecuencia). Entonces, en esta colección, las frecuencias son claves y las palabras son valores. Dado que muchas palabras pueden tener la misma frecuencia (y la va a usar como una clave), no puede usar ni Dictionary ni SortedDictionary (requieren claves únicas). Esto te deja con una SortedList.

No entiendo por qué insiste en mantener un enlace al elemento original en su diccionario principal / primer.

Si los objetos en su colección tuvieran una estructura más compleja (más campos) y necesitara poder acceder / ordenarlos eficientemente usando varios campos diferentes como claves, probablemente necesitaría una estructura de datos personalizada que consistiría en el almacenamiento principal que admite la inserción y eliminación de O (1) (LinkedList) y varias estructuras de indexación: Diccionarios / SortedDictionaries / SortedLists. Estos índices utilizarían uno de los campos de su clase compleja como clave y un puntero / referencia al LinkedListNode en LinkedList como valor.

Tendría que coordinar las inserciones y eliminaciones para mantener sus índices sincronizados con la colección principal (LinkedList) y las eliminaciones serían bastante caras, creo. Esto es similar a cómo funcionan los índices de base de datos: son fantásticos para las búsquedas, pero se convierten en una carga cuando necesita realizar muchas inserciones y eliminaciones.

Todo lo anterior solo se justifica si va a realizar un procesamiento pesado de búsqueda. Si solo necesita generarlos una vez ordenados por frecuencia, entonces podría producir una lista de tuplas (anónimas):

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
fuente
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
fuente
12

O por diversión, podría usar algunas bondades de extensión 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
fuente
10

Ordenar una SortedDictionarylista para enlazar en un ListViewcontrol usando 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
fuente
7

La forma más fácil de obtener un diccionario ordenado es usar la SortedDictionaryclase integrada :

//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 contiene la versión ordenada de sections

Alex Ruiz
fuente
77
Como mencionas en tu comentario, SortedDictionaryordena por teclas. El OP quiere ordenar por valor. SortedDictionaryNo ayuda en este caso.
Marty Neal
Bueno ... Si él / ella (usted) puede, simplemente configure los valores como las claves. Tomé el tiempo de las operaciones y sorteddictionary()siempre gané al menos 1 microsegundo, y es mucho más fácil de administrar (ya que la sobrecarga de convertirlo nuevamente en algo que interactúa y se maneja de manera similar a un Diccionario es 0 (ya es a sorteddictionary)).
mbrownnyc
2
@mbrownnyc - no, hacer eso requiere la suposición o condición previa de que los VALORES son únicos, lo cual no está garantizado.
Roger Willcocks
6

Las otras respuestas son buenas, si todo lo que quiere es tener una lista "temporal" ordenada por Valor. Sin embargo, si desea ordenar un diccionario Keyque se sincronice automáticamente con otro diccionario ordenado Value, puede usar la Bijection<K1, K2>clase .

Bijection<K1, K2> le permite inicializar la colección con dos diccionarios existentes, por lo que si desea que uno de ellos no esté ordenado y desea que el otro esté ordenado, puede crear su biyección con un código como

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

Puede usarlo dictcomo cualquier diccionario normal (se implementa IDictionary<K, V>) y luego llamar dict.Inversepara obtener el diccionario "inverso" que se ordena porValue .

Bijection<K1, K2>es parte de Loyc.Collections.dll , pero si lo desea, simplemente puede copiar el código fuente en su propio proyecto.

Nota : En caso de que haya varias claves con el mismo valor, no puede usarlas Bijection, pero podría sincronizar manualmente entre una ordinaria Dictionary<Key,Value>y una BMultiMap<Value,Key>.

Qwertie
fuente
Similar a http://stackoverflow.com/questions/268321 pero puede reemplazar cada diccionario con SortedDictionary. Aunque las respuestas no parecen admitir valores duplicados (supone 1 a 1).
crokusek
3

Supongamos que tenemos un diccionario como

   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) puedes usar 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
fuente
3

En realidad, en C #, los diccionarios no tienen métodos sort (), ya que está más interesado en ordenar por valores, no puede obtener valores hasta que les proporcione la clave, en resumen, necesita iterar a través de ellos, utilizando Ordenar por 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
}

puedes hacer un truco

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

o

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

también depende de qué tipo de valores esté almacenando,
ya sea individual (como string, int) o múltiple (como List, Array, clase definida por el usuario),
si solo puede hacer una lista, aplique el orden.
si es una clase definida por el usuario, entonces esa clase debe implementar IComparable
ClassName: IComparable<ClassName>y anular, compareTo(ClassName c) ya que son más rápidos que LINQ y están más orientados a objetos.

Ashish Kamble
fuente
0

Espacio de nombres requerido: 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);

Ordenar por desc:

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

Ordenar por Asc:

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

Puede ordenar el Diccionario por valor y obtener el resultado en el diccionario usando el código a continuación:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
pawan Kumar
fuente
8
Al volver a colocar los elementos ordenados en un diccionario, ya no se garantiza que se ordenarán cuando enumere el nuevo diccionario.
Marty Neal
2
¿Y por qué agrega esta respuesta cuando ya está respondida?
nawfal