sorted()
documentation(强调我的):
内置的sorted()函数保证稳定。一种是 如果保证不改变元素的相对顺序,则稳定 比较相等 [...]
在下面的代码中,键8
和16
具有相同的值,即它们将比较key=lambda x: d[x]
:
d = {8: 0, 16: 0, 'a': 1}
for i in range(200):
print(sorted(d, key=lambda x: d[x]))
# Prints always the same
# [8, 16, 'a']
# [8, 16, 'a']
# [8, 16, 'a']
# ......
现在让我们尝试对字典进行一些小修改:
示例1:
for i in range(10):
if i == 5:
del d[8]
del d[16]
d.update({16: 0})
d.update({8: 0})
print(sorted(d, key=lambda x: d[x]))
# Prints:
# [8, 16, 'a']
# [8, 16, 'a']
# [8, 16, 'a']
# [8, 16, 'a']
# [8, 16, 'a']
# [16, 8, 'a'] <----- it changed the order
# [16, 8, 'a']
# [16, 8, 'a']
# [16, 8, 'a']
# [16, 8, 'a']
id(d)保持不变。内容保持不变。密钥插入的顺序有什么变化。
注意:
选择这些键以便它们导致hash collisions:
8是占一个位置还是16占一个位置 第一次到达聚会
示例2:
d = {8: 0, 16: 0, 'a': 1}
c = {16: 0, 8: 0, 'a': 1}
print(sorted(d, key=lambda x: d[x]))
print(sorted(c, key=lambda x: d[x]))
我知道d
和c
在这里是不同的对象:
id(d) # 140067442777736
id(c) # 140067442811016
但我希望sorted()
以d == c
完全相同的方式处理对象。
示例3: 另一个例子如下:
d = {'a': 0, 'b': 0, 'c': 0, 'd': 1}
print(sorted(d, key=lambda x: d[x]))
在多个不同的运行中运行此操作(不且带有for
循环)每次都会给出不同的顺序。
注意:假设您使用Python 3.3 or higher和PYTHONHASHSEED=random
问题:
订单不稳定:
上面提到的订单不稳定是一个错误还是我错过了什么?我是否因为期望所有3个示例都有稳定的订单而误解了文档?
编辑:
这不是重复的。 副本中没有答案可以回答sorted()
的此行为是否有意。
我知道为什么排序行为的方式(我甚至故意引起了这种行为)。我不知道的是文档是否遗漏了什么,以及我在阅读时是否得出了错误的结论。
答案 0 :(得分:5)
这不是一个错误,你错过了一些东西。您操纵字典更改迭代次序,并且sorted()
保持稳定的顺序。或者换句话说,sorted()
保持输入顺序稳定,你通过更改字典来改变输入顺序。
请注意,sorted()
不会在此处“看到”字典。它传递了一个键序列,就像你首先在字典上使用list()
一样。在这种情况下,8
和16
都散列到同一个散列表槽。首先列出哪一个取决于插入的顺序:
>>> d1 = {}
>>> d1[8] = None
>>> d1[16] = None
>>> d1
{8: None, 16: None}
>>> list(d1)
[8, 16]
>>> d2 = {}
>>> d2[16] = None
>>> d2[8] = None
>>> d2
{16: None, 8: None}
>>> list(d2)
[16, 8]
在两个词典上调用list()
表示密钥以不同的顺序列出。 sorted()
只是维护这个顺序,因为它在字典上进行迭代,因为它们都按值分类在同一位置。这正是文档告诉你的事情。
请注意,字典相等根本没有任何作用。这不是文档中 compare equal 部分所指的内容。这只是指元素 序列;如果元素相等(或者在这种情况下,如果key(element)
值相等),则这些元素保持其相对顺序。
所以要关注你错过的可能事情:
方法的签名是sorted(iterable[, key][, reverse])
;关键词是 iterable
。文档的第一行:
从 iterable 中的项目返回新的排序列表。
强调我的;它是来自可迭代的项目,已排序。 Python术语表defines iterable
为:
一个能够一次返回一个成员的对象。迭代的示例包括所有序列类型(例如
list
,str
和tuple
)和一些非序列类型(如dict
),文件对象和任何类的对象您使用__iter__()
或__getitem__()
方法定义。 [...]当可迭代对象作为参数传递给内置函数iter()
时,它返回对象的迭代器。这个迭代器适用于一组值的传递。
任何需要迭代的东西基本上都会调用iter()
来循环生成的序列。
字典恰好是可迭代的,迭代会为您提供密钥。请参阅mapping types documentation:
<强>
iter(d)
强>
在字典的键上返回一个迭代器。这是iter(d.keys())
的快捷方式。
然后dict.keys()
文档指向dictionary views section,其中指出:
<强>
iter(dictview)
强>
在字典中的键,值或项(表示为(键,值)的元组)上返回一个迭代器。键和值以任意顺序迭代,这是非随机的,因Python实现而异,并取决于字典的插入和删除历史。如果迭代键,值和项视图而没有对字典的干预修改,则项的顺序将直接对应。这允许使用
zip()
创建(值,键)对:pairs = zip(d.values(), d.keys())
。另一种创建相同列表的方法是pairs = [(v, k) for (k, v) in d.items()]
。
再一次,强调我的。所以sorted()
迭代,将项目排序。迭代时,字典会生成顺序不稳定的键。
最后,您引用的部分从不与此相矛盾:
内置
sorted()
功能保证稳定。如果排序保证不改变比较相等的元素的相对顺序,则排序是稳定的。
因此迭代序列中的元素不会改变顺序。没有任何地方表明词典在迭代时不能产生不同的顺序。
换句话说,如果此处 iterable_a == iterable_b 并不重要,它与可迭代等式无关,只有元素相等对排序顺序稳定性很重要。如果迭代顺序不同,则该订单保持稳定。