Як сортувати словник за значенням?


796

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

Є SortedListкоефіцієнт, який корисний для одного значення (скажімо частота), що я хочу повернути його до слова.

Сортування замовлень словника за ключем, а не значення. Деякі вдаються до користувацького класу , але чи є чистіший спосіб?


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));
Артуро

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пункт.


15
Як я можу змінити sortedDict назад у словник <string, int>? Опубліковано нове ТАКЕ запитання тут: stackoverflow.com/questions/3066182/…
Kache

1
На жаль, це не працює на VS2005 через .net Framework 2.0 там (немає LINQ). Добре мати також відповідь Бамбрика.
Smalcat

22
Я не впевнений, що це завжди працює, тому що ітерація над словником не гарантує, що KeyValuePairs "потягнуться" в тому ж порядку, в який вони були вставлені. Ergo, не має значення, якщо ви використовуєте orderby в LINQ, оскільки словник може змінювати порядок вставлених елементів. Зазвичай працює як очікувалося, але немає гарантії, особливо для великих словників.
Бозидар Собчак

16
Тип повернення має бути IEnumerable<KeyValuePair<TKey, TValue>>або an OrderedDictionary<TKey, TValue>. Або слід використовувати a SortedDictionaryз самого початку. Для звичайної DictionaryMSDN чітко зазначено "Порядок повернення елементів не визначений." Здається, винна в останній редакції @ rythos42. :)
Борис Б.

16
Будь ласка, не .ToDictionary
враховуйте

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

6
Я не впевнений, чому це рішення не є більш популярним - можливо, тому, що для нього потрібен .NET 3.5?
Контанго

33
Це хороше рішення, але воно повинно мати це право перед закінченням крапки з двокрапкою: .ToDictionary (pair => pair.Key, pair => pair.Value);
theJerm

3
@theJerm, повернувши відсортовані предмети до словника, чи гарантовано замовлення? Це може працювати сьогодні, але це не гарантується.
nawfal

1
Використовуючи рамку 4.5, щойно перевірив, що вона не вимагає повернення до словника.
Jagd

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.


Я намагався сортувати словник, додаючи 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)- лише невелике доповнення до вашої відповіді :) Спасибі, btw :)
Андрій Нарушевічус

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 -> string> nextPair stringfirstPair -> string> firstPair
Art

2
Ідеальне рішення, що не стосується Linq. Мене ніколи не перестає дивувати те, як люди відчувають потребу в застосуванні Linq, навіть коли для вирішення проблеми абсолютно не потрібно. З C # 3, я вважаю, ви також можете спростити сортування, щоб просто використовувати лямбда: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));

25

Ви ніколи не зможете сортувати словник. Вони фактично не замовлені. Гарантії для словника полягають у тому, що колекції ключів та значень є ітерабельними, а значення можна отримати за індексом чи ключем, але немає жодної гарантії будь-якого конкретного замовлення. Отже, вам знадобиться потрапити до списку значень імен.


2
Але відсортований словник може дати список пар ключових значень.
рекурсивна

1
@recursive Будь-який словник повинен отримати це. Цікаво зауважити, що моя відповідь, яка є правильною, але неповною (могла б зробити те, що було зроблено на кращих прикладах), проголосується нижче недійсної відповіді, що призведе до винятку щодо дублюючих значень у оригінальному словнику (клавіші унікальні, значення не гарантуються бути)
Роджер Віллокс

5
Це відповідь bes, тому що словник не можна сортувати. Він стирає ключі, і ви можете виконати надзвичайно швидку операцію пошуку на ньому.
Paulius Zaliaduonis

@NetMage Так. Але інша частина проблеми полягає в тому, що вони хотіли замовити Value. І ви могли це зробити, лише помінявши ключ і значення. І значення не обов'язково унікальне, але Ключовим має бути.
Роджер

Так, але я вважаю, що ваша відповідь неправильна через абсолютні твердження в ній.
NetMage

21

Ви не сортуєте записи в словнику. Клас словників у .NET реалізований як хештебль - ця структура даних не може бути сортована за визначенням.

Якщо вам потрібно мати можливість переглядати колекцію (за клавішею) - вам потрібно використовувати SortedDictionary, який реалізований як двійкове дерево пошуку.

У вашому випадку, проте джерельна структура не має значення, оскільки вона сортується за іншим полем. Вам все одно потрібно буде сортувати його за частотою і помістити в нову колекцію, відсортовану за відповідним полем (частотою). Тож у цій колекції частоти - це клавіші, а слова - значення. Оскільки багато слів можуть мати однакову частоту (і ви будете використовувати його як ключ), ви не можете використовувати ні Словник, ні SortedDictionary (для них потрібні унікальні ключі). Це залишає вас з сортованим списком.

Я не розумію, чому ви наполягаєте на підтримці посилання на початковий елемент у головному / першому словнику.

Якщо об’єкти у вашій колекції мали складнішу структуру (більше полів), і вам потрібно було мати можливість ефективно отримувати доступ / сортувати їх за допомогою декількох різних полів як ключів - Вам, ймовірно, потрібна спеціальна структура даних, яка буде складатися з основного сховища, яке підтримує введення та видалення O (1) (LinkedList) та декілька структур індексації - Словники / Сортовані словники / Сортировані списки. Ці індекси будуть використовувати одне з полів вашого складного класу як ключ і вказівник / посилання на 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);

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

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>

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 (це вже є 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>.


Подібно до http://stackoverflow.com/questions/268321, але може замінити кожен словник сортованим словником. Хоча відповіді виглядають не в підтримці повторюваних значень (припускає 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'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
}

ви можете зробити одну хитрість,

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

або

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

його також залежить від того, які саме значення ви зберігаєте, чи
є одиничними (як рядок, 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);

Порядок на замовлення:

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

Порядок замовлення:

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

-2

Враховуючи, що у вас є словник, ви можете сортувати їх безпосередньо за значеннями, використовуючи нижче одного вкладиша:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.