按列表中元素出现次数对列表进行排序

22
我希望能够按照列表中元素出现的次数对列表进行排序。
当我使用以下形式时:

A=[2,1,3,4,2,2,3]
A.sort(key=lambda x:A.count(x))  
print(A)

结果不是我想要的: [2, 1, 3, 4, 2, 2, 3].
但是,当我使用sorted时,它会像这样:

B=sorted(A,key=lambda x:A.count(x))
print(B)

结果正确:[1, 4, 3, 3, 2, 2, 2]
这种行为的原因是什么?


3
顺便提一下,你不需要使用 lambda,例如 A.sort(key = A.count) - Chris_Rands
这将返回A中每个元素出现的次数:[A.count(element) for element in set(A)] - UpSampler
2
在这里使用 Counter (A.sort(key=collections.Counter(A).get)) 会更有效率,并且适用于 sortsorted - Faibbus
Python对象分为可变和不可变两种类型。如果可变对象中的值发生更改,它将影响原始数据。因此,list object(列表对象)是可变的,数据的索引在动态改变。 - Fatih1923
4个回答

18
这是有意为之的设计。在使用原地排序时,CPython暂时“禁止”访问列表,此行为在此处有说明:

CPython实现细节:在对列表进行排序时,试图改变甚至检查列表的效果是未定义的。 Python的C实现使得列表在此期间为空,并且如果它能检测到列表在排序期间已被修改,则会引发ValueError。

您可以通过在键函数中打印A来检查这一点-您将获得一个空列表
In [2]: def key_function(x):
    ...:     print(A, x)
    ...:     return A.count(x)
    ...: 

In [3]: A.sort(key=key_function)  
([], 2)
([], 1)
([], 3)
([], 4)
([], 2)
([], 2)
([], 3)

但是,如果你对 sorted() 进行这样的操作:

In [4]: sorted(A, key=key_function)
([2, 1, 3, 4, 2, 2, 3], 2)
([2, 1, 3, 4, 2, 2, 3], 1)
([2, 1, 3, 4, 2, 2, 3], 3)
([2, 1, 3, 4, 2, 2, 3], 4)
([2, 1, 3, 4, 2, 2, 3], 2)
([2, 1, 3, 4, 2, 2, 3], 2)
([2, 1, 3, 4, 2, 2, 3], 3)
Out[4]: [1, 4, 3, 3, 2, 2, 2]

这也被记录在sort()实现中:

/* 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).
 */.

5
仅仅因为有文档记录,并不意味着它就不糟糕 :) - Jean-François Fabre
2
这个限制可能不适用于 key= 函数。我建议在 http://bugs.python.org/ 提交一个错误报告。 - zwol
2
哇!违反了“最小惊奇原则”。在我看来,一个错误会是一种改进。 - jpmc26
3
在我看来,那似乎不是一个实现细节。 - dstromberg

6

在原地排序过程中,似乎A被改变了,因此您不能在排序过程中依赖A的值。

复制一份也可以解决问题。

A=[2,1,3,4,2,2,3]
B=A[:]
A.sort(key=lambda x:B.count(x))
print(A)

在Python文档中的这行代码得到了证实:python documentation

CPython实现细节:在对列表进行排序时,试图改变或检查列表的效果是未定义的。Python的C实现会在此期间使列表看起来为空,并在检测到列表在排序期间被修改时引发ValueError异常。


2
我不确定这是一个完整的答案,似乎更像是一个猜测 ;) - Chris_Rands
1
猜对了 :) - Jean-François Fabre
@Chris_Rands,这原本只是我的猜测,但现在我在文档中找到了支持。看来被接受的答案一开始就是正确的 :) - Jean-François Fabre

2
我认为这是因为在计算过程中 A.sort 改变了列表的值。而 sorted() 不改变列表,因此返回正确的结果。

1
内置的sorted 会将提供的序列转换为列表,然后根据键参数进行排序(省略错误检查):
/* copy sequence provided */
newlist = PySequence_List(seq);

/* get list.sort for the list object */
callable = _PyObject_GetAttrId(newlist, &PyId_sort);

/* call it and then return later on */
v = _PyObject_FastCallKeywords(callable, args + 1, nargs - 1, kwnames);

这基本上相当于Jean在他的回答中提供的内容:
B = list(A)
B.sort(key=lambda x: A.count(x))

通过制作副本 B 并在 key 函数中引用 A,这将消除由 A.sort 强加的无法查看自身的限制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接