使用list.count使用.sort()就地对列表进行排序不起作用。为什么?

时间:2019-06-20 23:34:30

标签: python list sorting

我正在尝试按其元素的频率对列表进行排序。

>>> a = [5, 5, 4, 4, 4, 1, 2, 2]
>>> a.sort(key = a.count)
>>> a
[5, 5, 4, 4, 4, 1, 2, 2]

a不变。但是:

>>> sorted(a, key = a.count)
[1, 5, 5, 2, 2, 4, 4, 4]

为什么此方法不适用于.sort()

2 个答案:

答案 0 :(得分:6)

您看到的是list.sort的某些 CPython实现细节的结果。再试一次,但是首先创建a的副本:

a.sort(key=a.copy().count)
a
# [1, 5, 5, 2, 2, 4, 4, 4]

.sort在内部修改a,因此a.count将产生不可预测的结果。这是documented作为实现细节。

copy调用所做的是创建一个a的副本,并使用那个列表的count方法作为键。您可以看到一些调试语句会发生什么:

def count(x):
    print(a)
    return a.count(x)

a.sort(key=count)
[]
[]
[]
...
a内访问

.sort时会显示为空白列表,并且[].count(anything)将是0。这就解释了为什么输出与输入相同-谓词都相同(0)。

OTOH,sorted创建了一个新列表,因此它没有这个问题。


如果您真的想按频率计数排序,惯用的方法是使用Counter

from collections import Counter

a.sort(key=Counter(a).get)
a
# [1, 5, 5, 2, 2, 4, 4, 4]

答案 1 :(得分:4)

它不适用于list.sort方法,因为CPython决定暂时“清空列表”(other answer already presents this)。 documentation as implementation detail中提到了这一点:

  

CPython实现细节:在对列表进行排序时,尝试更改甚至检查列表的效果是不确定的。 Python的C实现使列表在整个持续时间内都显示为空,并在可以检测到列表在排序过程中被突变的情况下引发ValueError

source code包含类似的注释,但有更多解释:

    /* The list is temporarily made empty, so that mutations performed
     * by comparison functions can't affect the slice of memory we're
     * sorting (allowing mutations during sorting is a core-dump
     * factory, since ob_item may change).
     */

解释并不简单,但问题是键功能和比较可能会在排序期间更改list实例,这很可能导致C代码的不确定行为(可能会使解释器崩溃)。为了防止在排序过程中清空列表,因此即使有人更改了实例,也不会导致解释器崩溃。

sorted不会发生这种情况,因为sorted copies the listsimply sorts the copy。副本在排序过程中仍为空,但是无法访问它,因此它不可见。


但是,您实际上不应该像这样进行排序以获得频率排序。这是因为对于每个项目,您只需调用一次key函数。并且list.count遍历每个项目,因此您可以有效地遍历每个元素的整个列表(这称为O(n**2)复杂性)。更好的方法是为每个元素计算一次频率(可以在O(n)中完成),然后在key中进行访问。

但是,由于CPython具有Counter类,它也支持most_common,因此您真的可以使用它:

>>> from collections import Counter
>>> [item for item, count in reversed(Counter(a).most_common()) for _ in range(count)]
[1, 2, 2, 5, 5, 4, 4, 4]

这可能会改变具有相等计数的元素的顺序,但是由于您执行的频率计数没有多大关系。