Bagaimana Anda mengurutkan kamus berdasarkan nilai?

796

Saya sering harus mengurutkan kamus, yang terdiri dari kunci & nilai, berdasarkan nilai. Sebagai contoh, saya memiliki hash kata dan frekuensi masing-masing, yang ingin saya pesan berdasarkan frekuensi.

Ada SortedListyang bagus untuk nilai tunggal (katakanlah frekuensi), yang ingin saya petakan kembali ke kata.

Perintah SortedDictionary menurut kunci, bukan nilai. Beberapa resor ke kelas khusus , tetapi apakah ada cara yang lebih bersih?

Kalid
sumber
1
Selain hanya menyortir kamus (seperti dalam jawaban yang diterima), Anda juga bisa hanya membuat ICompareryang melakukan trik (benar bahwa itu menerima kunci untuk dibandingkan, tetapi dengan kunci, Anda bisa mendapatkan nilai). ;-)
BrainSlugs83

Jawaban:

520

Menggunakan:

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

Karena Anda menargetkan .NET 2.0 atau di atasnya, Anda dapat menyederhanakan ini menjadi sintaks lambda - ini setara, tetapi lebih pendek. Jika Anda menargetkan .NET 2.0, Anda hanya dapat menggunakan sintaks ini jika menggunakan kompiler dari Visual Studio 2008 (atau lebih tinggi).

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));
Leon Bambrick
sumber
26
Saya menggunakan solusi ini (Terima kasih!) Tetapi bingung selama satu menit sampai saya membaca posting Michael Stum (dan cuplikan kodenya dari John Timney) dan menyadari bahwa myList adalah objek sekunder, daftar KeyValuePairs, yang dibuat dari kamus, dan kemudian disortir.
Robin Bennett
113
itu satu liner - Anda tidak perlu kawat gigi. dapat ditulis ulang sebagaimyList.Sort((x,y)=>x.Value.CompareTo(y.Value));
Arnis Lapsa
25
Untuk mengurutkan turun, ganti x dan y pada perbandingan: myList.Sort ((x, y) => y.Value.CompareTo (x.Value));
Arturo
8
Saya pikir perlu dicatat bahwa ini memerlukan Linq untuk metode ekstensi ToList.
Ben
17
Kalian sedang menunggu untuk menyulitkan ini - kamus sudah mengimplementasikan IEnumerable, sehingga kamu bisa mendapatkan daftar yang diurutkan seperti ini:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList();
BrainSlugs83
523

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

Ini juga akan membuat Anda sangat fleksibel karena Anda dapat memilih 10, 20 10% teratas, dll. Atau jika Anda menggunakan indeks frekuensi kata untuk type-ahead, Anda juga dapat memasukkan StartsWithklausa.

caryden
sumber
15
Bagaimana saya bisa mengubah SortDict kembali menjadi Kamus <string, int>? Diposting pertanyaan SO baru di sini: stackoverflow.com/questions/3066182/…
Kache
1
Sayangnya ini tidak berfungsi pada VS2005 karena .net framework 2.0 di sana (tidak ada LINQ). Baik juga untuk memiliki jawaban Bambrick.
Smalcat
22
Saya tidak yakin apakah itu selalu berhasil karena iterasi kamus tidak menjamin bahwa KeyValuePairs "ditarik" dalam urutan yang sama seperti yang telah mereka masukkan. Ergo, tidak masalah jika Anda menggunakan orderby di LINQ karena Kamus dapat mengubah urutan elemen yang dimasukkan. Biasanya berfungsi seperti yang diharapkan tetapi TIDAK ADA JAMINAN, terutama untuk kamus besar.
Bozydar Sobczak
16
Jenis IEnumerable<KeyValuePair<TKey, TValue>>pengembalian harus atau a OrderedDictionary<TKey, TValue>. Atau seseorang harus menggunakan SortedDictionarydari awal. Untuk dataran Dictionary, MSDN dengan jelas menyatakan "Urutan pengembalian barang tidak ditentukan.". Tampaknya edit terakhir @ rythos42 yang harus disalahkan. :)
Boris B.
16
Harap abaikan semua saran .ToDictionary- kamus standar tidak menjamin urutan
AlexFoxGill
254
var ordered = dict.OrderBy(x => x.Value);
sean
sumber
6
Saya tidak yakin mengapa solusi ini tidak lebih populer - mungkin karena memerlukan. NET 3.5?
Contango
33
Ini adalah solusi yang baik, tetapi harus memiliki hak ini sebelum semi-colon akhir: .ToDictionary (pair => pair.Key, pair => pair.Value);
theJerm
3
@theJerm dengan mengembalikan item yang disortir ke dalam kamus, apakah pesanan dijamin? Mungkin bekerja hari ini, tetapi tidak dijamin.
nawfal
1
Menggunakan kerangka kerja 4,5, baru saja memverifikasi bahwa itu tidak memerlukan pemain kembali ke kamus.
Jagd
12
Seharusnya tidak ada pemain yang kembali ke kamus karena kamus tidak dipesan. Tidak ada jaminan KeyValuePairs akan tetap dalam urutan yang Anda inginkan.
David DeMar
165

Melihat sekeliling, dan menggunakan beberapa fitur C # 3.0 kita dapat melakukan ini:

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

Ini adalah cara terbersih yang pernah saya lihat dan mirip dengan cara Ruby menangani hash.

Kalid
sumber
Saya mencoba untuk mengurutkan kamus sambil menambahkan KeyValuePairs ke ComboBox ... ini berhasil! Terima kasih!
Jason Down
6
Jangan lupa untuk menambahkan namespace System.Linq saat menggunakan sintaks ini.
M. Dudley
4
(for KeyValuePair<string, int> item in keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)- hanya tambahan kecil untuk jawaban Anda :) Terima kasih, btw :)
Andrius Naruševičius
7
@ AndriusNaruševičius: Jika Anda menambahkan item yang dihasilkan kembali ke kamus, Anda akan menghancurkan pesanan, karena kamus tidak dijamin dipesan dengan cara tertentu .
ATAU Mapper
Ini berguna. Bagaimana bisa terbalik untuk pergi ke arah lain?
Dan Hastings
158

Anda dapat mengurutkan Kamus berdasarkan nilai dan menyimpannya kembali ke dalam kamus itu sendiri (sehingga saat Anda memeriksa nilai-nilai tersebut secara berurutan):

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

Tentu, itu mungkin tidak benar, tetapi berhasil.

Matt Frear
sumber
8
Anda juga dapat menggunakan OrderByDescending jika Anda ingin mengurutkan ke dalam daftar turun.
Mendokusai
Bekerja untuk saya, meskipun saya harus mengubahnya sedikit ke: Kamus <kunci, nilai> dict = dict.OrderBy (x => x.Value) .ToDictionary (x => x.Key, x => x.Value);
Josh
17
"Bekerja" ini tidak dijamin. Ini detail implementasi. Tidak perlu bekerja di lain waktu. Jawaban yang salah, diturunkan.
nawfal
4
Output Kamus TIDAK dijamin memiliki urutan pengurutan tertentu.
Roger Willcocks
5
Saya akan sangat prihatin melihat ini dalam kode produksi. Itu tidak dijamin dan bisa berubah kapan saja. Bukannya saya menghindar dari solusi pragmatis, itu hanya menunjukkan kurangnya pemahaman tentang struktur data imo.
jamespconnor
61

Pada level yang tinggi, Anda tidak punya pilihan lain selain menelusuri keseluruhan Kamus dan melihat setiap nilai.

Mungkin ini membantu: http://bytes.com/forum/thread563638.html Salin / Tempel dari 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
sumber
3
stringnextPair -> string> nextPair stringfirstPair -> string> firstPair
Seni
2
Solusi non-Linq sempurna. Tidak pernah berhenti membuat saya takjub bagaimana orang merasa perlu menggunakan Linq bahkan ketika itu sama sekali tidak diperlukan untuk menyelesaikan masalah. Dengan C # 3, saya percaya Anda juga dapat menyederhanakan Urutkan untuk hanya menggunakan lambda: myList.Sort ((x, y) => x.Value.CompareTo (y.Value));
25

Anda tidak akan pernah bisa mengurutkan kamus. Mereka sebenarnya tidak dipesan. Jaminan untuk kamus adalah bahwa kunci dan koleksi nilai dapat diubah, dan nilai dapat diambil dengan indeks atau kunci, tetapi tidak ada jaminan dari urutan tertentu. Karenanya Anda perlu memasukkan pasangan nilai nama ke dalam daftar.

Roger Willcocks
sumber
2
Kamus yang diurutkan dapat menghasilkan daftar pasangan nilai kunci.
Rekursif
1
@recursive Setiap kamus harus menghasilkan itu. Menarik untuk dicatat bahwa jawaban saya, yang benar, tetapi tidak lengkap (bisa melakukan apa yang dilakukan contoh yang lebih baik) dipilih di bawah jawaban yang tidak valid yang akan menghasilkan pengecualian pada nilai duplikat dalam kamus asli (kunci unik, nilai tidak dijamin menjadi)
Roger Willcocks
5
Ini adalah jawaban bes, karena Kamus tidak dapat diurutkan. Ini hashes Keys dan Anda dapat melakukan operasi pencarian yang sangat cepat di atasnya.
Paulius Zaliaduonis
@ NetMage Ya. Tetapi bagian lain dari masalah adalah bahwa mereka ingin dipesan oleh Value. Dan Anda hanya bisa melakukannya dengan menukar Key dan Value. Dan Nilai belum tentu unik, tetapi Kunci harus.
Roger Willcocks
Ya, tapi saya pikir jawaban Anda salah karena pernyataan absolut di dalamnya.
NetMage
21

Anda tidak mengurutkan entri dalam Kamus. Kelas kamus di .NET diimplementasikan sebagai hashtable - struktur data ini tidak dapat diurutkan berdasarkan definisi.

Jika Anda harus dapat mengulangi koleksi Anda (dengan kunci) - Anda perlu menggunakan SortedDictionary, yang diimplementasikan sebagai Binary Search Tree.

Dalam kasus Anda, namun struktur sumber tidak relevan, karena diurutkan berdasarkan bidang yang berbeda. Anda masih perlu mengurutkannya berdasarkan frekuensi dan memasukkannya ke dalam koleksi baru yang diurutkan berdasarkan bidang yang relevan (frekuensi). Jadi dalam koleksi ini frekuensinya adalah kunci dan kata-kata adalah nilai. Karena banyak kata dapat memiliki frekuensi yang sama (dan Anda akan menggunakannya sebagai kunci), Anda tidak dapat menggunakan Kamus maupun SortedDictionary (mereka memerlukan kunci unik). Ini meninggalkan Anda dengan SortedList.

Saya tidak mengerti mengapa Anda bersikeras mempertahankan tautan ke item asli di kamus utama / pertama Anda.

Jika objek dalam koleksi Anda memiliki struktur yang lebih kompleks (lebih banyak bidang) dan Anda harus dapat mengakses / mengurutkannya secara efisien menggunakan beberapa bidang berbeda sebagai kunci - Anda mungkin memerlukan struktur data khusus yang akan terdiri dari penyimpanan utama yang mendukung O (1) penyisipan dan penghapusan (LinkedList) dan beberapa struktur pengindeksan - Kamus / SortedDictionaries / SortedLists. Indeks ini akan menggunakan salah satu bidang dari kelas kompleks Anda sebagai kunci dan pointer / referensi ke LinkedListNode di LinkedList sebagai nilai.

Anda perlu mengoordinasikan penyisipan dan pemindahan untuk menjaga indeks Anda tetap sinkron dengan koleksi utama (LinkedList) dan pemindahan akan cukup mahal menurut saya. Ini mirip dengan cara kerja indeks basis data - mereka fantastis untuk pencarian tetapi mereka menjadi beban ketika Anda perlu melakukan banyak insetions dan delesi.

Semua hal di atas hanya dibenarkan jika Anda akan melakukan beberapa pencarian berat. Jika Anda hanya perlu menampilkannya sekali diurutkan berdasarkan frekuensi maka Anda bisa menghasilkan daftar tupel (anonim):

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

Atau untuk bersenang-senang Anda bisa menggunakan beberapa kebaikan ekstensi 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));
mitos
sumber
10

Mengurutkan SortedDictionarydaftar untuk diikat ke dalam ListViewkontrol menggunakan 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
sumber
7

Cara termudah untuk mendapatkan Kamus yang diurutkan adalah dengan menggunakan SortedDictionarykelas bawaan:

//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 akan berisi versi yang diurutkan dari sections

Alex Ruiz
sumber
7
Seperti yang Anda sebutkan dalam komentar Anda, SortedDictionaryurutkan berdasarkan kunci. OP ingin mengurutkan berdasarkan nilai. SortedDictionarytidak membantu dalam hal ini.
Marty Neal
Baiklah ... Jika dia (kamu) bisa, cukup atur nilainya sebagai kunci. Saya menghitung waktu operasi dan sorteddictionary()selalu menang setidaknya 1 mikrodetik, dan lebih mudah untuk mengelola (karena overhead mengubahnya kembali menjadi sesuatu yang mudah berinteraksi dengan dan dikelola mirip dengan Kamus adalah 0 (sudah menjadi sorteddictionary)).
mbrownnyc
2
@mbrownnyc - tidak, melakukan hal itu memerlukan asumsi atau prasyarat bahwa VALUE unik, yang tidak dijamin.
Roger Willcocks
6

Jawaban lain baik, jika semua yang Anda inginkan adalah memiliki daftar "sementara" diurutkan berdasarkan Nilai. Namun, jika Anda ingin memiliki kamus diurutkan menurut Keyyang secara otomatis mensinkronisasikan dengan kamus lain yang diurutkan oleh Value, Anda bisa menggunakan Bijection<K1, K2>kelas .

Bijection<K1, K2> memungkinkan Anda untuk menginisialisasi koleksi dengan dua kamus yang ada, jadi jika Anda ingin salah satunya tidak diurutkan, dan Anda ingin yang lainnya diurutkan, Anda dapat membuat bijection Anda dengan kode seperti

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

Anda dapat menggunakan dictseperti kamus normal (yang mengimplementasikannya IDictionary<K, V>), dan kemudian menelepon dict.Inverseuntuk mendapatkan kamus "terbalik" yang diurutkan berdasarkan Value.

Bijection<K1, K2>adalah bagian dari Loyc.Collections.dll , tetapi jika Anda mau, Anda bisa menyalin kode sumber ke proyek Anda sendiri.

Catatan : Seandainya ada beberapa kunci dengan nilai yang sama, Anda tidak bisa menggunakan Bijection, tetapi Anda bisa menyinkronkan secara manual antara yang biasa Dictionary<Key,Value>dan a BMultiMap<Value,Key>.

Qwertie
sumber
Mirip dengan http://stackoverflow.com/questions/268321 tetapi dapat mengganti setiap Kamus dengan SortedDictionary. Meskipun jawabannya terlihat tidak mendukung nilai duplikat (mengasumsikan 1 banding 1).
crokusek
3

Misalkan kita memiliki kamus sebagai

   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) Anda dapat menggunakan 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
sumber
3

Sebenarnya dalam C #, Kamus tidak memiliki metode sort (), karena Anda lebih tertarik mengurutkan berdasarkan nilai, Anda tidak bisa mendapatkan nilai sampai Anda memberikannya kunci, singkatnya, Anda perlu mengulanginya, menggunakan Pesanan LINQ oleh,

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
}

Anda dapat melakukan satu trik,

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

atau

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

ini juga tergantung pada jenis nilai apa yang Anda simpan,
apakah itu tunggal (seperti string, int) atau beberapa (seperti Daftar, Array, kelas yang ditentukan pengguna),
jika lajang Anda dapat membuat daftar itu lalu menerapkan sortir.
jika kelas yang ditentukan pengguna, maka kelas itu harus mengimplementasikan IComparable,
ClassName: IComparable<ClassName>dan menimpa compareTo(ClassName c) karena mereka lebih cepat daripada LINQ, dan lebih berorientasi objek.

Ashish Kamble
sumber
0

Namespace yang dibutuhkan: 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);

Dipesan oleh desc:

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

Dipesan oleh Asc:

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

Anda dapat mengurutkan Kamus berdasarkan nilai dan mendapatkan hasilnya dalam kamus menggunakan kode di bawah ini:

Dictionary <<string, string>> ShareUserNewCopy = 
       ShareUserCopy.OrderBy(x => x.Value).ToDictionary(pair => pair.Key,
                                                        pair => pair.Value);                                          
pawan Kumar
sumber
8
Dengan meletakkan kembali item yang diurutkan ke dalam kamus, item tersebut tidak lagi dijamin untuk disortir saat Anda menghitung kamus baru.
Marty Neal
2
Dan mengapa Anda menambahkan jawaban ini ketika ini sudah dijawab?
nawfal
-2

Mengingat Anda memiliki kamus, Anda dapat mengurutkannya langsung pada nilai menggunakan di bawah satu liner:

var x = (from c in dict orderby c.Value.Order ascending select c).ToDictionary(c => c.Key, c=>c.Value);
agaton
sumber
4
Jawaban ini salah, karena kamus yang dihasilkan tidak dijamin dipesan.
ATAU Mapper