我正在尝试按其元素的频率对列表进行排序。
>>> 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()
?
答案 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 list和simply 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]
这可能会改变具有相等计数的元素的顺序,但是由于您执行的频率计数没有多大关系。