如何按值对字典进行排序?

948

我经常需要对一个字典(包含键和值)按值进行排序。例如,我有一个单词和相应频率的哈希表,我想按频率排序。

有一个SortedList适用于单个值(比如频率),我想将其映射回单词。

SortedDictionary按键而不是值进行排序。一些人会使用自定义类,但是否有更简洁的方法呢?


2
除了仅仅对字典进行排序(如已接受的答案所述),您还可以创建一个IComparer来完成此操作(确实它接受要比较的键,但是有了键,您就可以获取值)。;-) - BrainSlugs83
3
“创建一个IComparer”并不是一个完整的解决方案。请说明如何使用该IComparer来产生排序结果。 - ToolmakerSteve
2
排序的字典没有意义,因为你是通过键来访问字典的。如果你想要一个按键和值排序的列表,请将其转换为列表,然后进行排序。 - Yarek T
你可以通过多种方式访问 yearek T,其中包括使用 ElementAt(int index) 方法。此外,你还可以使用 foreach 循环来按索引迭代。 - Barreto
22个回答

590

使用 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子句。


18
如何将 sortedDict 转换回 Dictionary<string, int>?在 Stack Overflow 上发布了新问题,链接为:https://dev59.com/cXA75IYBdhLWcg3w3NBe - Kache
22
我不确定它是否总是有效,因为迭代字典不能保证以插入顺序“提取”KeyValuePair。因此,在LINQ中使用orderby无关紧要,因为Dictionary可以改变插入元素的顺序。通常情况下,它按预期工作,但尤其是对于大型字典,没有任何保证。 - Bozydar Sobczak
16
返回类型应该是IEnumerable<KeyValuePair<TKey, TValue>>OrderedDictionary<TKey, TValue>,或者一开始就使用SortedDictionary。对于普通的Dictionary,MSDN明确说明:“返回项的顺序是未定义的”。看起来@rythos42的最新编辑有责任。 :) - Boris B.
21
请忽略所有关于 .ToDictionary 的建议 - 标准字典不保证排序顺序 - Alex
1
我刚刚使用了一个简单的方法来计时与其他最高评分答案(使用Stopwatch())进行比较,并发现使用LINQ的时间增加了近350%。使用其他方法排序的四个成员列表= 0.0039511秒;使用LINQ方法排序的相同四个成员列表= 0.0130195秒。 - brandeded
显示剩余6条评论

572

使用:

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或更高版本,因此可以将其简化为lambda语法--它是等效的,但更短。如果您的目标是.NET 2.0,则只有在使用来自Visual Studio 2008(或更高版本)的编译器时才能使用此语法。

var myList = aDictionary.ToList();

myList.Sort((pair1,pair2) => pair1.Value.CompareTo(pair2.Value));

27
我用了这个解决方案(谢谢!),但是在读了Michael Stum的文章(以及他从John Timney那里贴出的代码片段)之后,我有一分钟的困惑,意识到myList是一个次要对象,即由字典创建并排序的KeyValuePair列表。 - Robin Bennett
117
如果只有一行代码,你不需要花括号。可以将其重写为 myList.Sort((x,y)=>x.Value.CompareTo(y.Value)); - Arnis Lapsa
27
要按降序排列,请在比较中交换x和y:myList.Sort((x, y) => y.Value.CompareTo(x.Value)); - Arturo
8
值得注意的是,这需要使用 Linq 来调用 ToList 扩展方法。 - Ben
37
你们过于复杂化了这个问题,因为字典已经实现了 IEnumerable 接口,所以你可以像这样获取一个排序后的列表:var mySortedList = myDictionary.OrderBy(d => d.Value).ToList(); - BrainSlugs83
显示剩余4条评论

364

你可以使用以下代码:

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

43
这是一个不错的解决方案,但在终止分号之前应该加上这个:.ToDictionary(pair => pair.Key, pair => pair.Value); - theJerm
我更喜欢这个,简洁明了。@Gravitas: 我同意,并且 OP 没有提到框架版本。 - Andreas
因为它需要转换回字典,这并不总是直截了当的... - MoonKnight
6
把排序好的项目放回字典,那么顺序就有保障了吗?它今天可能有效,但并不保证。 - nawfal
3
使用4.5框架,刚刚验证了它不需要将其转换回字典。 - Jagd
20
不应将其转换回字典,因为字典是无序的。不能保证键值对的顺序始终符合您的预期。 - David DeMar

187

您可以按值对字典进行排序,并将其保存回原字典(以便在使用 foreach 循环时,值按顺序输出):

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

当然,这可能不正确,但它是可行的。海伦定律意味着这很有可能会继续起作用。


13
如果想将列表按降序排序,你可以使用 OrderByDescending。 - Mendokusai
22
这个“工作”的结果并不被保证。这只是一种实现细节,有可能在其他情况下无法正常工作。回答错误会被扣分。 - nawfal
6
字典的输出结果并不保证有任何特定的排序顺序。 - Roger Willcocks
7
如果我看到这段代码在生产环境中使用,我会非常担心。它没有得到保证,随时可能发生变化。虽然我不回避实用的解决方案,但在我看来这表明对数据结构的理解不足。 - jamespconnor
4
虽然这个方法“可行”,但是由于字典不保证顺序,你应该省略.ToDictionary(...)。这样会将其转换回字典。只需执行dict.OrderBy(...)即可;这将生成所需的枚举器。 - ToolmakerSteve
显示剩余4条评论

178

通过观察并利用一些C# 3.0特性,我们可以做到这一点:

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

这是我见过的最干净的方式,类似于处理哈希的Ruby方式。


7
使用这个语法时,请不要忘记添加 System.Linq 命名空间。 - sourcenouveau
4
(对于keywordCounts.OrderBy(key => key.Value) select item).ToDictionary(t => t.Key, t => t.Value)这段代码)只是我答案的一个小补充 :) 顺便说一句,谢谢你 :) - Andrius Naruševičius
10
如果你将结果再加入到一个字典中,你会破坏顺序,因为字典不保证按特定方式排序 - O. R. Mapper

66

总的来说,你别无选择,只能遍历整个字典并查看每个值。

也许这个链接会有所帮助: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);
    }
);

2
完美的非 Linq 解决方案。令我惊讶的是,即使在绝对不需要使用 Linq 来解决问题时,人们仍然觉得有必要使用它。使用 C# 3,我认为您还可以通过使用 Lambda 简化 Sort:myList.Sort((x, y) => x.Value.CompareTo(y.Value)); - user502255

29

无论如何,您都无法对字典进行排序。它们实际上并没有被排序。字典的保证是键和值集合是可迭代的,并且可以通过索引或键检索值,但没有任何特定顺序的保证。因此,您需要将名称值对放入列表中。


3
一个排序过的字典可以返回一个键-值对列表。 - recursive
2
@recursive 任何字典都应该能够产生这样的结果。有趣的是,我的答案是正确的,但不完整(可以像更好的例子那样做),而被投票排在一个无效的答案之下,在原始字典中重复值会导致异常(键是唯一的,值不能保证)。 - Roger Willcocks
5
这是最佳答案,因为字典不可排序。它通过哈希键来实现,并且您可以在其中执行非常快速的查找操作。 - Paulius Zaliaduonis
@NetMage 是的。但问题的另一部分是他们想要按值排序。而你只能通过交换键和值来实现这一点。而值不一定是唯一的,但键必须是唯一的。 - Roger Willcocks
1
是的,但我认为你的答案不正确,因为其中有绝对性的陈述。 - NetMage
@NetMage,而且OP表示SortedDictionary不合适。 - Roger Willcocks

26

在词典中,您不需要对条目进行排序。.NET中的Dictionary类是作为散列表实现的 - 根据定义,此数据结构无法进行排序。

如果您需要能够按键迭代您的集合 - 您需要使用SortedDictionary,它是作为二叉搜索树实现的。

然而,在您的情况下,源结构并不重要,因为它已经按不同字段排序了。您仍需要按频率排序并将其放入新集合中,该集合按相关字段(频率)排序。因为许多单词可能具有相同的频率(并且您将将其用作键),所以既不能使用Dictionary也不能使用SortedDictionary(它们需要唯一的键)。这将使您使用SortedList。

我不明白为什么您坚持保留到您的主/第一个字典中的原始项的链接。

如果您的集合中的对象具有更复杂的结构(更多字段)并且您需要能够有效地使用多个不同字段作为键访问/排序它们 - 您可能需要一个自定义数据结构,该数据结构由支持O(1)插入和删除(LinkedList)的主存储器和几个索引结构组成 - Dictionaries/SortedDictionaries/SortedLists。这些索引将使用您复杂类的一个字段作为键,并使用指向LinkedListNode中的指针/引用作为值。

您需要协调插入和删除以使索引与主集合(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);
}

1
从计算机科学的角度来看,这实际上是最好的答案。它不是最简单或最快的,需要评估实际问题,而不是试图给出不存在的“快速配方”。 我想许多程序员对“似乎可行”的解决方案感到满意。直到它不再奏效。 但愿许多人能更好地了解... - Alberto Chiesa
我来到这里是值得的(结束)。 - Gray Programmerz

14

您可以使用:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接