¿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?


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

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.


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.


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

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.


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.


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

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.


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

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

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>

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


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>.


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

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.


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.
}

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

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.