Как вы сортируете словарь по значению?

796

Мне часто приходится сортировать словарь, состоящий из ключей и значений, по значению. Например, у меня есть хэш слов и соответствующих частот, которые я хочу упорядочить по частоте.

Существует вариант, SortedListкоторый подходит для одного значения (скажем, частоты), и я хочу сопоставить его со словом.

SortedDictionary заказывает по ключу, а не по значению. Некоторые прибегают к пользовательскому классу , но есть ли более чистый способ?

Kalid
источник
1
Помимо простой сортировки словаря (как в принятом ответе), вы также можете просто создать функцию, IComparerкоторая делает трюк (правда, она принимает ключ для сравнения, но с помощью ключа вы можете получить значение). ;-)
BrainSlugs83

Ответы:

520

Использование:

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

Поскольку вы ориентируетесь на .NET 2.0 или выше, вы можете упростить это до лямбда-синтаксиса - он эквивалентен, но короче. Если вы ориентируетесь на .NET 2.0, вы можете использовать этот синтаксис, только если вы используете компилятор из Visual Studio 2008 (или выше).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Леон Бамбрик
источник
26
Я использовал это решение (спасибо!), Но был в замешательстве в течение минуты, пока не прочитал пост Майкла Стума (и его фрагмент кода от Джона Тимни) и понял, что myList - это вторичный объект, список KeyValuePairs, который создается из словаря, а потом отсортировано.
Робин Беннетт
113
это один лайнер - вам не нужны брекеты. это может быть переписано какmyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Арнис Лапса
25
Для сортировки по убыванию переключите x и y на сравнение: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Я думаю, что стоит отметить, что для этого требуется Linq для метода расширения ToList.
Бен
17
Вы, ребята, недоумеваете, усложнив это - словарь уже реализован IEnumerable, так что вы можете получить отсортированный список, подобный этому:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

Используйте 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;

Это также обеспечит большую гибкость в том, что вы можете выбрать верхние 10, 20, 10% и т. Д. Или, если вы используете свой индекс частоты слов для type-ahead, вы также можете включить StartsWithпредложение.

caryden
источник
15
Как я могу изменить sortedDict обратно в словарь <string, int>? Выложен новый ТАК вопрос здесь: stackoverflow.com/questions/3066182/…
Kache
1
К сожалению, это не работает на VS2005 из-за .net Framework 2.0 там (без LINQ). Хорошо также иметь ответ Бамбрика.
Смалькат
22
Я не уверен, работает ли он всегда, потому что перебор словаря не гарантирует, что KeyValuePairs «вытянуты» в том же порядке, в котором они были вставлены. Поэтому не имеет значения, используете ли вы orderby в LINQ, поскольку Dictionary может изменить порядок вставленных элементов. Обычно он работает как положено, но ГАРАНТИИ НЕТ, особенно для больших словарей.
Божидар Собчак
16
Тип возврата должен быть IEnumerable<KeyValuePair<TKey, TValue>>или OrderedDictionary<TKey, TValue>. Или следует использовать SortedDictionaryс самого начала. Для простого DictionaryMSDN ясно заявляет, что "порядок, в котором возвращаются элементы, не определен". Похоже, что последняя редакция @ rythos42 виновата. :)
Борис Б.
16
Пожалуйста, не .ToDictionary
обращайте
254
var ordered = dict.OrderBy(x => x.Value);
Sean
источник
6
Я не уверен, почему это решение не более популярно - возможно, потому что для него требуется .NET 3.5?
Контанго
33
Это хорошее решение, но оно должно иметь это право перед конечной точкой с запятой: .ToDictionary (pair => pair.Key, pair => pair.Value);
TheJerm
3
@theJerm, поместив отсортированные элементы обратно в словарь, тогда порядок гарантирован? Это может работать сегодня, но это не гарантировано.
Nawfal
1
Используя платформу 4.5, только что убедился, что она не требует приведения обратно в словарь.
Джагд
12
Не должно быть преобразования в словарь, потому что словари не упорядочены. Нет никакой гарантии, что KeyValuePairs останется в том порядке, который вы хотите.
Дэвид ДеМар
165

Оглядываясь вокруг, и используя некоторые функции C # 3.0, мы можем сделать это:

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

Это самый чистый способ, который я видел, и он похож на способ обработки хэшей в Ruby.

Kalid
источник
Я пытался отсортировать словарь при добавлении KeyValuePairs в ComboBox ... это работало отлично! Спасибо!
Джейсон Даун
6
Не забудьте добавить пространство имен System.Linq при использовании этого синтаксиса.
М. Дадли
4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- просто небольшое дополнение к вашему ответу :) Спасибо, кстати :)
Andrius Naruševičius
7
@ AndriusNaruševičius: Если вы добавите полученные элементы обратно в словарь, вы уничтожите порядок, поскольку словари не гарантированно будут заказываться каким-либо конкретным способом .
ИЛИ Mapper
Это было удобно. Как это можно перевернуть, чтобы пойти другим путем?
Дэн Гастингс
158

Вы можете отсортировать словарь по значению и сохранить его обратно в себе (так что, когда вы просматриваете его, значения выводятся по порядку):

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

Конечно, это может быть не правильно, но это работает.

Мэтт Фреар
источник
8
Вы также можете использовать OrderByDescending, если хотите отсортировать по убыванию.
Мендокусай
Работал для меня, хотя мне пришлось немного изменить его на: Словарь <ключ, значение> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Джош
17
Это «работа» не гарантируется. Это деталь реализации. Это не должно работать в другое время. Неправильный ответ.
nawfal
4
Выходной словарь НЕ гарантированно имеет какой-либо определенный порядок сортировки.
Роджер Уиллкокс
5
Я был бы весьма обеспокоен, увидев это в рабочем коде. Это не гарантируется и может измениться в любое время. Не то чтобы я уклонялся от прагматичных решений, это просто показывает отсутствие понимания структуры данных imo.
jamespconnor
61

На высоком уровне у вас нет другого выбора, кроме как пройти весь словарь и посмотреть на каждое значение.

Может быть, это поможет: http://bytes.com/forum/thread563638.html Копирование / вставка от Джона Тимни:

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 -> строка> nextPair stringfirstPair -> строка> firstPair
Art
2
Идеальное решение, отличное от Linq. Меня не перестает удивлять то, как люди чувствуют необходимость использовать Linq, даже если это абсолютно не требуется для решения проблемы. Я полагаю, что в C # 3 вы также можете упростить сортировку, чтобы просто использовать лямбду: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

Вы никогда не сможете отсортировать словарь в любом случае. Они на самом деле не заказаны. Гарантии для словаря состоят в том, что наборы ключей и значений являются итеративными, а значения могут быть получены по индексу или ключу, но нет гарантии какого-либо конкретного порядка. Следовательно, вам нужно получить пару имя-значение в список.

Роджер Уиллкокс
источник
2
Сортированный словарь может дать список пар ключ-значение.
рекурсивный
1
@recursive Любой словарь должен дать это. Интересно отметить, что мой ответ, который является правильным, но неполным (мог бы сделать то, что сделали лучшие примеры), голосуется ниже недопустимого ответа, что приведет к исключениям для дублирующихся значений в исходном словаре (ключи уникальны, значения не гарантируются быть)
Роджер Уиллкокс
5
Это лучший ответ, потому что словарь не сортируется. Он хеширует ключи, и вы можете выполнить на нем чрезвычайно быструю операцию поиска.
Паулюс Залиадуонис
@NetMage Да. Но другая часть проблемы заключается в том, что они хотели упорядочить по стоимости. И вы могли бы сделать это только путем замены ключа и значения. И Значение не обязательно уникально, но Ключ должен быть.
Роджер Уиллкокс
Да, но я думаю, что ваш ответ неверен из-за абсолютных утверждений в нем.
NetMage
21

Вы не сортируете записи в Словаре. Класс словаря в .NET реализован как хеш-таблица - эта структура данных не сортируется по определению.

Если вам нужно иметь возможность перебирать свою коллекцию (по ключу) - вам нужно использовать SortedDictionary, который реализован в виде дерева двоичного поиска.

В вашем случае, однако, структура источника не имеет значения, потому что она сортируется по другому полю. Вам все равно нужно отсортировать его по частоте и поместить в новую коллекцию, отсортированную по соответствующему полю (частоте). Таким образом, в этой коллекции частоты являются ключами, а слова - значениями. Поскольку многие слова могут иметь одинаковую частоту (и вы собираетесь использовать его в качестве ключа), вы не можете использовать ни Dictionary, ни SortedDictionary (для них требуются уникальные ключи). Это оставляет вас с SortedList.

Я не понимаю, почему вы настаиваете на сохранении ссылки на оригинальный элемент в вашем основном / первом словаре.

Если объекты в вашей коллекции имеют более сложную структуру (больше полей) и вам необходимо иметь возможность эффективно обращаться к ним / сортировать их, используя несколько различных полей в качестве ключей - вам, вероятно, понадобится настраиваемая структура данных, которая будет состоять из основного хранилища, которое поддерживает вставку и удаление O (1) (LinkedList) и несколько структур индексации - Словари / SortedDictionaries / SortedLists. Эти индексы будут использовать одно из полей вашего сложного класса в качестве ключа и указатель / ссылку на LinkedListNode в LinkedList в качестве значения.

Вам нужно было бы координировать вставки и удаления, чтобы синхронизировать ваши индексы с основной коллекцией (LinkedList), и удаления, я думаю, будет довольно дорогим. Это похоже на работу индексов базы данных - они отлично подходят для поиска, но становятся бременем, когда вам нужно выполнить много вставок и удалений.

Все вышеперечисленное оправдано только в том случае, если вы собираетесь выполнить некоторую сложную обработку. Если вам нужно вывести их только один раз по частоте, вы можете просто создать список (анонимных) кортежей:

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);
mrfazolka
источник
12

Или для удовольствия вы можете использовать некоторые расширения 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
источник
10

Сортировка SortedDictionaryсписка для привязки к элементу ListViewуправления с использованием 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
источник
7

Самый простой способ получить отсортированный словарь - использовать встроенный SortedDictionaryкласс:

//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 будет содержит отсортированную версию sections

Алекс Руис
источник
7
Как вы упоминаете в своем комментарии, SortedDictionaryсортировка по ключам. ОП хочет отсортировать по значению. SortedDictionaryне помогает в этом случае.
Марти Нил
Ну ... Если он / она (вы) можете, просто установите значения в качестве ключей. Я рассчитывал время выполнения операций и sorteddictionary()всегда выигрывал, по крайней мере, на 1 микросекунду, и им намного легче управлять (поскольку издержки преобразования его обратно во что-то, с чем легко взаимодействовать и управлять им, как в словаре, равны 0 (это уже a sorteddictionary)).
mbrownnyc
2
@mbrownnyc - нет, для этого требуется допущение или предварительное условие, что ЗНАЧЕНИЯ уникальны, что не гарантируется.
Роджер Уиллкокс
6

Другие ответы хороши, если все, что вам нужно, это иметь «временный» список, отсортированный по значению. Однако, если вы хотите, чтобы словарь, отсортированный по ним Key, автоматически синхронизировался с другим словарем, который сортируется Value, вы можете использовать Bijection<K1, K2>класс .

Bijection<K1, K2> позволяет инициализировать коллекцию двумя существующими словарями, поэтому, если вы хотите, чтобы один из них не был отсортирован, а другой - отсортирован, вы можете создать свою биекцию с кодом, подобным

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

Вы можете использовать dictкак любой обычный словарь (он реализует IDictionary<K, V>), а затем вызывать, dict.Inverseчтобы получить «обратный» словарь, который сортируется поValue .

Bijection<K1, K2>является частью Loyc.Collections.dll , но если вы хотите, вы можете просто скопировать исходный код в ваш собственный проект.

Примечание . Если имеется несколько ключей с одинаковым значением, вы не можете их использовать Bijection, но вы можете вручную синхронизировать обычное Dictionary<Key,Value>и a BMultiMap<Value,Key>.

Qwertie
источник
Аналогично http://stackoverflow.com/questions/268321, но может заменить каждый словарь на SortedDictionary. Хотя ответы выглядят не для поддержки повторяющихся значений (предполагается, что от 1 до 1).
crokusek
3

Предположим, у нас есть словарь

   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) вы можете использовать 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

На самом деле в C # словари dint имеют методы sort (), так как вас больше интересует сортировка по значениям, вы не можете получать значения до тех пор, пока не предоставите их ключ, короче говоря, вам нужно перебирать их, используя LINQ 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
}

ты можешь сделать один трюк,

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

или

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

Он также зависит от того, какие значения вы храните
: одиночные (например, string, int) или множественные (например, List, Array, пользовательский класс),
если вы можете создать один из них, то примените сортировку.
если пользовательский класс, то этот класс должен реализовывать IComparable
ClassName: IComparable<ClassName>и переопределять, так compareTo(ClassName c) как они быстрее, чем LINQ, и более объектно-ориентированы.

Ашиш Камбл
источник
0

Обязательное пространство имен: 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);

Сортировать по desc:

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

Заказ по Asc:

foreach (KeyValuePair<string, int> kvp in counts.OrderBy(key => key.Value))
{
// some processing logic for each item if you want.
}
Джейдип шил
источник
-2

Вы можете отсортировать словарь по значению и получить результат в словаре, используя код ниже:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
Паван Кумар
источник
8
Поместив отсортированные элементы обратно в словарь, они больше не гарантируют сортировку при перечислении нового словаря.
Марти Нил
2
И почему вы добавляете этот ответ, когда на него уже есть ответ?
Nawfal