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

3414

我有一个从数据库中读取的键值对字典:一个字符串字段和一个数字字段。字符串字段是唯一的,所以它是字典的键。

我可以按照键排序,但是如何基于值排序呢?

注意:我已经在Stack Overflow上阅读了这里的问题:How do I sort a list of dictionaries by a value of the dictionary?,并且可能可以更改我的代码以使用字典列表,但由于我实际上不需要字典列表,我想知道是否有更简单的解决方案来升序或降序排序。


9
字典数据结构没有固有的顺序。虽然可以遍历它,但不能保证遍历的顺序是特定的。这是设计上的考虑,因此最好使用另一种数据结构来表示。 - Daishiman
135
"sorted()" 函数可以用于字典(并返回已排序的键列表),因此我认为他知道这一点。如果不了解其程序,就告诉别人他们使用了错误的数据结构是荒谬的。如果你需要90%的时间快速查找,则可能需要使用字典。 - bobpaul
这里清晰简洁地介绍了排序字典的三种输出方式(键、值、两者):https://dev59.com/nGQn5IYBdhLWcg3wg3aR - JStrahl
2
@Daishiman 基类可能没有排序,但是OrderedDict当然是有序的。 - Taylor D. Edmiston
1
在Python 3.6+中,字典保留插入顺序。当然,这并不意味着可以按值对它们进行排序,但另一方面,不能再说“字典数据结构没有固有的顺序”了。 - Konrad Kocik
34个回答

6870

Python 3.7+ 或 CPython 3.6

在 Python 3.7+ 中,字典保留插入顺序。同样在 CPython 3.6 中也是如此,但是这是一个实现细节

>>> x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
>>> {k: v for k, v in sorted(x.items(), key=lambda item: item[1])}
{0: 0, 2: 1, 1: 2, 4: 3, 3: 4}
或者
>>> dict(sorted(x.items(), key=lambda item: item[1]))
{0: 0, 2: 1, 1: 2, 4: 3, 3: 4}

旧版 Python

无法对字典进行排序,只能获取一个已排序的字典表示。字典本质上是无序的,但其他数据类型(例如列表和元组)则不是。因此,你需要一个有序的数据类型来表示已排序的值,这将是一个列表,可能是由元组组成的列表。

例如:

import operator
x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
sorted_x = sorted(x.items(), key=operator.itemgetter(1))

sorted_x 将会是一个按每个元组中第二个元素排序后的元组列表。 dict(sorted_x) == x

对于那些希望按键而不是值进行排序的人:

import operator
x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
sorted_x = sorted(x.items(), key=operator.itemgetter(0))

在Python3中,由于不能使用解包(unpacking),我们可以使用

x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
sorted_x = sorted(x.items(), key=lambda kv: kv[1])

如果你想要字典格式的输出,你可以使用 collections.OrderedDict

import collections

sorted_dict = collections.OrderedDict(sorted_x)

51
关于各种按值排序的字典的时间安排,请参考以下链接:http://writeonly.wordpress.com/2008/08/30/sorting-dictionaries-by-value-in-python-improved/ - Gregg Lind
203
sorted_x.reverse()会按第二个元组元素进行降序排序。 - saidimu apale
515
既然жҲ‘们已з»ҸдҪҝз”ЁдәҶsorted()еҮҪж•°пјҢдј е…Ҙreverse=TrueеҸӮж•°дјҡжӣҙеҠ й«ҳж•ҲгҖӮ - rmh
131
在Python 3中,我使用了一个lambda函数:sorted(d.items(), key=lambda x: x[1])。这能在Python 2.x中工作吗? - Benbob
4
请问我在哪里可以阅读有关使用key=lambda item: item[1]的更多信息?我不太理解的部分是item[1],这是因为当我们执行x.items()时它返回键值对,通过这种方式我们可以通过item[1]来访问值吗? - UdonN00dle
显示剩余7条评论

1603

简单得像这样:sorted(dict1, key=dict1.get)

实际上,可以通过"按字典值排序"来完成。最近我在一个Code Golf(Stack Overflow问题 Code golf: Word frequency chart )中需要进行这样的操作。简化一下问题:给定一段文本,计算每个单词出现的次数,并以按频率递减排序的顺序显示前几个单词。

如果你创建一个以单词为键、出现次数为值的字典,可以像这样简便地进行排序:

from collections import defaultdict
d = defaultdict(int)
for w in text.split():
    d[w] += 1
然后,您可以使用 sorted(d, key=d.get) 来获取单词列表,并按使用频率排序 - 排序将遍历字典键,使用单词出现次数作为排序键。
for w in sorted(d, key=d.get, reverse=True):
    print(w, d[w])

我写下这份详细的解释,以说明人们通常所说的“我可以轻松按键对字典进行排序,但如何按值进行排序”,我认为原始帖子试图解决这个问题。解决方案是根据值对键进行排序,如上所示。


40
这也不错,但*key=operator.itemgetter(1)key=d.get*更具可扩展性和效率。 - smci
12
@bli sorted_keys = sorted(d.items(), key=itemgetter(1), reverse=True)for key, val in sorted_keys: print "%s: %d" % (key, val) - itemgetter 在被调用时会创建一个函数,你不会像例子中那样直接使用它。在字典上进行简单迭代时,只使用键而不使用值。 - Izkata
33
我来自未来,想告诉你关于collections.Counter这个类,它有一个most_common方法可能会让你感兴趣 :) - Eevee
2
@Eevee 的有趣事实是,Counter 对象是在 3.1 版本中新引入的(发布于2009年),因此这个答案一直过时了 :-) - dantiston

1144

你可以使用:

sorted(d.items(), key=lambda x: x[1])

这将按照字典中每个条目的值从小到大对字典进行排序。

如果要按降序排序,只需添加reverse=True

sorted(d.items(), key=lambda x: x[1], reverse=True)
抱歉,我的中文水平还不能满足您的需求。我是一名英语语言模型,可以帮助您回答关于英语语言的问题。
d = {'one':1,'three':3,'five':5,'two':2,'four':4}
a = sorted(d.items(), key=lambda x: x[1])    
print(a)

输出:

[('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]

从我所看到的(http://docs.python.org/2/library/collections.html?highlight=ordereddict#ordereddict-examples-and-recipes),有一个名为OrderedDict的类,可以排序并保留顺序,同时仍然是一个字典。从代码示例中,您可以使用lambda对其进行排序,但我个人还没有尝试过:P - UsAndRufus
75
我个人更倾向于使用 key=lambda (k, v): v - Claudiu
@Keyo 那不应该返回一个按值排序的键列表(而不是(k,v)元组)吗?这是我在Python 2.7.10中得到的结果。 @Nyxynyx 添加参数reverse=True以按降序排序。 - dhj
50
@Claudiu,我也喜欢(k,v)这种语法,但它在Python 3中不可用。其中tuple parameter unpacking已被删除。 - Bob Stein
2
如果你将这个内容放在 OrderedDict() 实例中,你将得到一个(有序的)字典而不是元组列表! - tsveti_iko
显示剩余2条评论

265

字典无法排序,但可以从中构建一个排序列表。

一个按值排序的字典列表:

sorted(d.values())

按值排序的(键,值)对列表:

from operator import itemgetter
sorted(d.items(), key=itemgetter(1))

具有相同值的键放置在什么顺序?我首先按键排序,然后按值排序,但具有相同值的键的顺序不保持不变。 - SabreWolfy
7
自CPython 3.6起,字典现在可以进行排序,而所有其他Python实现也从3.7开始支持此功能。 - user3064538
当时是真的,但现在Python字典默认会保留插入项的顺序。因此它们可以进行排序。 - c8999c 3f964f64

181

在最新的Python 2.7中,我们有了新的OrderedDict类型,它会记住向其中添加项的顺序。

>>> d = {"third": 3, "first": 1, "fourth": 4, "second": 2}

>>> for k, v in d.items():
...     print "%s: %s" % (k, v)
...
second: 2
fourth: 4
third: 3
first: 1

>>> d
{'second': 2, 'fourth': 4, 'third': 3, 'first': 1}

从原始字典中按值排序,创建一个新的有序字典:

>>> from collections import OrderedDict
>>> d_sorted_by_value = OrderedDict(sorted(d.items(), key=lambda x: x[1]))

OrderedDict与普通字典的行为类似:

>>> for k, v in d_sorted_by_value.items():
...     print "%s: %s" % (k, v)
...
first: 1
second: 2
third: 3
fourth: 4

>>> d_sorted_by_value
OrderedDict([('first': 1), ('second': 2), ('third': 3), ('fourth': 4)])

6
不是关于维护键的顺序,而是关于“按值排序”的问题。 - Nas Banov
10
@Nas Banov: 它并不是按照关键字排序,而是按照我们创建项目的顺序排序。在我们的情况下,我们按值进行排序。不幸的是,由于选择了一个只有3个项目的字典,所以无论按值还是按键排序,顺序都是相同的,因此我扩展了示例字典。 - mykhal
sorted(d.items(), key=lambda x: x[1]) 你能解释一下 x 的含义吗?为什么它可以在 lambda 中使用 x[1],而不能是 x[0] 呢?非常感谢! - JZAU
1
@Boern d.items() 返回一个类似列表的容器,其中包含 (key, value) 元组。[0] 访问元组的第一个元素 -- 键 -- [1] 访问第二个元素 -- 值。 - BallpointBen
2
注意:从3.6版本开始(作为CPython/PyPy的实现细节),以及从3.7版本开始(作为Python语言的保证),普通的dict也是有序的,因此在运行于现代Python上的代码中,您可以将OrderedDict直接替换为dict。除非您需要重新排列现有dict的顺序(使用move_to_end/popitem)或需要等式比较具有顺序敏感性,否则不再真正需要OrderedDict。它比普通的dict使用更多的内存,因此如果可以的话,应该使用dict - ShadowRanger
显示剩余2条评论

124

使用Python 3.5

虽然我发现接受的答案很有用,但我也很惊讶它没有更新以引用标准库collections模块中的OrderedDict作为一种可行的、现代的替代方案——旨在解决这种类型的问题。

from operator import itemgetter
from collections import OrderedDict

x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
sorted_x = OrderedDict(sorted(x.items(), key=itemgetter(1)))
# OrderedDict([(0, 0), (2, 1), (1, 2), (4, 3), (3, 4)])

官方的OrderedDict文档也提供了一个非常类似的例子,但是使用lambda作为排序函数:

# regular unsorted dictionary
d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2}

# dictionary sorted by value
OrderedDict(sorted(d.items(), key=lambda t: t[1]))
# OrderedDict([('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)])

你能解释一下在这个例子中itemgetter是做什么的吗?否则这似乎和使用lambda一样晦涩。 - c8999c 3f964f64

115

基本上与Hank Gay的答案几乎相同:

sorted([(value,key) for (key,value) in mydict.items()])

根据John Fouhy的建议进行了轻微优化:

sorted((value,key) for (key,value) in mydict.items())

10
和Hank Gay的回答一样,您不需要使用方括号。sorted()函数可以轻松地接受任何可迭代对象,例如生成器表达式。 - John Fouhy
你可能仍然需要交换(value,key)元组元素,以得到(key, value)。然后需要另一个列表推导式。[(key, value) for (value, key) in sorted_list_of_tuples] - saidimu apale
2
不,最好保留方括号,因为sorted仍然需要重建列表,而从gencomp重建会更快。对于代码高尔夫来说很好,但对于速度来说不好。保留丑陋的([])版本。 - Jean-François Fabre
我有点困惑,这返回的是一个元组数组而不是字典。在我看来,你漏掉了字典推导式部分:{x: v for x, v in sorted((value, key) for (key, value) in mydict.items())} - melMass

87

自从Python 3.6版本起,内置字典将是有序的

好消息,因此OP最初的用例,将从一个数据库检索到的成对映射,其中唯一的字符串ID作为键,数字值作为值,插入到Python v3.6+内置字典中时,现在应该会保留插入顺序。

比如说,如果从数据库查询中得到了两列表达式:

SELECT a_key, a_value FROM a_table ORDER BY a_value;

这些信息将会被存储在两个Python元组中,k_seq和v_seq(通过数字索引对齐,并且长度相同),然后:

k_seq = ('foo', 'bar', 'baz')
v_seq = (0, 1, 42)
ordered_map = dict(zip(k_seq, v_seq))

允许以后输出为:

for k, v in ordered_map.items():
    print(k, v)

在这种情况下,yielding 意味着(适用于新的 Python 3.6+ 内置字典!):
foo 0
bar 1
baz 42

按照v的值相同的顺序排列。

在我的计算机上安装的Python 3.5中,它目前产生的结果为:

bar 1
foo 0
baz 42

详细信息:

在2012年由Raymond Hettinger提出(参见python-dev邮件,主题为"More compact dictionaries with faster iteration"),并且现在(2016年)在Victor Stinner的python-dev邮件中宣布,主题为"Python 3.6 dict becomes compact and gets a private version; and keywords become ordered",由于修复/实现了Python 3.6中的问题27350 "Compact and ordered dict", 我们现在能够使用内置的dict来保持插入顺序!

希望这将导致一个薄层有序字典实现作为第一步。正如@JimFasarakis-Hilliard所指出的,一些人也认为OrderedDict类型在未来也会有用途。我认为整个Python社区都会仔细检查这是否经得起时间的考验以及下一步的步骤。

是时候重新思考我们的编码习惯,以不错过通过稳定排序实现的可能性:

  • 关键字参数和
  • (中间)字典存储

第一种情况是因为它在某些情况下可以简化函数和方法实现的分发。

第二种情况是因为它鼓励更容易地在处理管道中使用dict作为中间存储。

Raymond Hettinger友好地提供了解释“Python 3.6字典背后的技术”的文档-来自他2016年12月8日的旧金山Python Meetup Group演示。

也许很多Stack Overflow高度装饰的问题和答案页面将收到这些信息的变体,许多高质量的答案也需要进行每个版本的更新。

买方须知(但请参见下面的更新2017-12-15):

正如@ajcr所指出的:“这种新实现的有序保留方面被认为是一个实现细节,不应该依赖它。”(来自whatsnew36)不是吹毛求疵,但是引用有点悲观;-)。它继续说“(这可能会在未来改变,但希望在更改语言规范以强制所有当前和未来的Python实现具有有序保留语义之前,在语言中使用几个版本此新dict实现; 这也有助于保持向后兼容性,其中随机迭代顺序仍然有效,例如Python 3.5)。"

因此,就像某些人类语言(例如德语)一样,用法塑造了语言,现在已经宣布了意愿... 在whatsnew36中。

更新2017-12-15:

Guido van Rossum在写给python-dev列表的邮件中表示:

就这样吧。"Dict keeps insertion order"成为了规定。 谢谢!

因此,版本3.6 CPython字典插入顺序的副作用现在成为语言规范的一部分(不再仅是实现细节)。该邮件线程还揭示了一些区别设计目标的collections.OrderedDict,正如Raymond Hettinger在讨论期间提醒的那样。


@ajcr 感谢您的警告,非常感激 - 因为我的回复中编织了表情符号和可能性,这些应该表明,变化是巨大的,但当然,仅适用于CPython(参考实现)和PyPy。说到完全不同的事情...当编写人机指令时,我很少谈论非实现细节。如果只是Jython ;-) ...我可能没有勇气写下它。 - Dilettant
OrderedDict 绝对不会被删除;相反,它将成为当前字典实现的一个薄包装器(因此您可能还可以添加它将变得更加紧凑)。由于这会让读者误以为 OrderedDict 没有用处,因此在 ImportError 中添加该片段并不是最好的想法。 - Dimitris Fasarakis Hilliard
作为对这个答案和结构化字典的回应,我发布了一个新的答案。欢迎提供反馈! - Bram Vanroy

84

使用namedtuple通常非常方便。例如,您有一个以'name'为键,'score'为值的字典,并且想要按'score'进行排序:

import collections
Player = collections.namedtuple('Player', 'score name')
d = {'John':5, 'Alex':10, 'Richard': 7}

按最低分数进行排序:

worst = sorted(Player(v,k) for (k,v) in d.items())

按最高分数进行排序:

best = sorted([Player(v,k) for (k,v) in d.items()], reverse=True)

现在你可以通过如下极具Python风格的方式获取第二名选手(索引为1)的名称和分数:

player = best[1]
player.name
    'Richard'
player.score
    7

我该如何将它转换回字典? - rowana
as_list=[Player(v,k) for (k,v) in d.items()] as_dict=dict((p.name,p.score) for p in as_list) - Remi

59

从Python 3.6开始,dict对象现在按插入顺序排序。这已经正式写入到Python 3.7的规格说明中。

>>> words = {"python": 2, "blah": 4, "alice": 3}
>>> dict(sorted(words.items(), key=lambda x: x[1]))
{'python': 2, 'alice': 3, 'blah': 4}

在那之前,你必须使用 OrderedDict

Python 3.7文档 表示:

从版本3.7开始更改:字典顺序保证是插入顺序。这种行为是CPython从3.6开始的实现细节。


2
很棒!dict(sorted(words.items(), key=lambda x: x[1], reverse=True)) 用于降序排序。 - vizyourdata

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