【问题标题】:sorted() order unstable under certain conditions. Is this intentional? [duplicate]sorted() 顺序在某些条件下不稳定。这是故意的吗? [复制]
【发布时间】:2015-09-13 09:23:16
【问题描述】:

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


问题:
订单不稳定:

  • 对于获得其顺序的同一对象 已修改(示例 1)。
  • 对于比较相等的 2 个对象(示例 2)。
  • 对于完全相同的代码的不同运行(示例 3)。

上面提到的订单不稳定是错误还是我遗漏了什么?我是否期望所有 3 个示例都有一个稳定的顺序,从而误解了文档?

【问题讨论】:

  • 是什么让您认为 sorted 有问题?键的顺序发生了变化。 sorted() 保持你给它的相对顺序。给它[16, 8] 不同于给它[8, 16]。
  • @MartijnPieters 它的文档让我觉得在不同的运行中,它会给出相同的结果。它没有。是的,[16,8] != [8,16]。 但是 {16,8} == {8,16}.
  • 所以?仅仅因为字典相等并不意味着 sorted 可以看到或关心。它以给定的顺序被赋予一系列键。你改变了这个顺序。
  • 但您根本没有根据16 或8 进行排序。您正在根据字典中的相应值进行排序。如果值是相同的排序将使用最先出现的元素。试试像 - sorted([3,4,5,1,2],key=lambda x:1) 这样的东西,你真的希望 sorted 改变顺序吗?
  • 不是sorted() 改变了顺序,sorted() 是从字典本身获取改变的顺序。

标签: python sorting python-3.4


【解决方案1】:

这不是错误,你错过了一些东西。您操纵了字典,改变了迭代的顺序,而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。文档第一行:

    可迭代中从项目返回一个新的排序列表。

    强调我的;排序的是来自可迭代对象的项目。 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)
    返回字典中键、值或项(表示为 (key, value) 的元组)的迭代器。

    键和值以任意顺序迭代,这是非随机的,因 Python 实现而异,并且取决于字典的插入和删除历史。如果键、值和项目视图被迭代而没有对字典进行干预修改,项目的顺序将直接对应。这允许使用zip():pairs = zip(d.values(), d.keys()) 创建(值,键)对。创建相同列表的另一种方法是pairs = [(v, k) for (k, v) in d.items()]。

    再次强调我的。所以sorted() 迭代,对项目进行排序。字典在迭代时会产生顺序不稳定稳定的键。

  • 最后,你引用的部分,从不与此相矛盾:

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

    所以迭代序列中的元素不会改变顺序。然而,这并没有说明字典在迭代时不能产生不同的顺序。

    换句话说,这里是否 iterable_a == iterable_b 无关紧要,这与可迭代相等无关,只有 元素相等 对排序顺序的稳定性很重要。如果迭代顺序不同,该顺序保持稳定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-15
    • 1970-01-01
    • 2018-07-08
    • 2012-06-03
    • 1970-01-01
    • 1970-01-01
    • 2016-01-27
    相关资源
    最近更新 更多