sorted()命令在某些条件下不稳定。这是故意的吗?

时间:2015-09-13 09:23:17

标签: python sorting python-3.4

sorted() documentation(强调我的):

  

内置的sorted()函数保证稳定。一种是   如果保证不改变元素的相对顺序,则稳定   比较相等 [...]

在下面的代码中,键816具有相同的值,即它们将比较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]))

我知道dc在这里是不同的对象:

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 higherPYTHONHASHSEED=random

问题:
订单不稳定:

  • 获取其订单的同一对象 修改(例1)。
  • 用于比较相等的2个对象(示例2)。
  • 用于完全相同代码的不同运行(示例3)。

上面提到的订单不稳定是一个错误还是我错过了什么?我是否因为期望所有3个示例都有稳定的订单而误解了文档?

编辑: 这不是重复的。 副本中没有答案可以回答sorted()的此行为是否有意。 我知道为什么排序行为的方式(我甚至故意引起了这种行为)。我不知道的是文档是否遗漏了什么,以及我在阅读时是否得出了错误的结论。

1 个答案:

答案 0 :(得分:5)

这不是一个错误,你错过了一些东西。您操纵字典更改迭代次序,并且sorted()保持稳定的顺序。或者换句话说,sorted()保持输入顺序稳定,通过更改字典来改变输入顺序。

请注意,sorted()不会在此处“看到”字典。它传递了一个键序列,就像你首先在字典上使用list()一样。在这种情况下,816都散列到同一个散列表槽。首先列出哪一个取决于插入的顺序:

>>> 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为:

      

    一个能够一次返回一个成员的对象。迭代的示例包括所有序列类型(例如liststrtuple)和一些非序列类型(如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 并不重要,它与可迭代等式无关,只有元素相等对排序顺序稳定性很重要。如果迭代顺序不同,则该订单保持稳定。