列表.count()在列表中有两个项时返回一个项

4
我已经用Python编写了一些代码,用于从列表中删除唯一的数字,因此给定输入:

[1,2,3,2,1]

应该返回

[1,2,2,1]

但我的程序返回

[1,2,1]

我的代码如下:

for i in data:
    if data.count(i) == 1:
        data.pop(i)

我发现错误出现在 if data.count(i) == 1:。它说当列表中有两个2时,data.count(2) == 1。我不明白为什么会给出错误的答案。
7个回答

6
如果您有一个长列表,应该将所有数字放入Counter(iterable) - dictionary中。
from collections import Counter
data = [1,2,3,2,1]
c = Counter(data)

cleaned = [x for x in data if c[x] > 1]

print(cleaned)

这将在您的列表上进行一次遍历(O(n)),并查找创建的字典中出现次数的频率是O(1)。总体而言,这比使用列表推导式要快得多。
result = [x for x in data if data.count(x) > 1]

对于一个包含100个值的列表,它将遍历你的100个值100次,以计算每一个单独的值,这是O(n^2) - 不好的事情。

输出:

[1,2,2,1]

4
尝试将新内容添加到一个新列表中,而不是修改您的旧列表:
res = []
data = [1,2,3,2,1]

for i in data:
    if data.count(i) > 1:
        res.append(i)

在迭代期间更改列表大小是不良的实践,而pop会做到这一点。这将返回res = [1, 2, 2, 1]

3

这是一个递归问题。你误解了list.pop()。它需要一个索引而不是特定的元素。因此,你没有删除你期望的内容。

在这里要做的事情是使用enumerate

data = [1,2,3,2,1]

#You could use dup_list = data[:] for python 3.2 and below
dup_list = data.copy()

for index,item in enumerate(dup_list):
    if dup_list.count(item) == 1:
        data.pop(index)

这样你可以弹出正确索引的项目。

编辑

我进行了编辑,感谢@wim的评论。现在我正在迭代原始列表的副本(dup_list),以便不同时迭代和改变原始列表。

此外,我明确地创建了一个副本,旨在说明。但是你可以使用代码的更短版本,

data = [1,2,3,2,1]

#using data[:] to iterate over a copy
for index,item in enumerate(data[:]):
    if data.count(item) == 1:
        data.pop(index)

请注意,我添加了注释,因为这种语法可能会使一些人感到困惑。

1
你关于 list.pop 的部分是正确的。但是提出的替代方案在迭代列表时仍然会改变它,这可能会产生不可预测的结果。 - wim
@wim 我添加了一个重复列表。 - scharette
仅迭代数据切片:for i, item in enumerate(data[:]):也可以工作。 这将是最短的代码(无需声明副本),但我不确定是否推荐这种做法。 - michaPau

3

使用列表推导式的解决方案

我认为更符合Python风格的答案是使用列表推导式:

result = [x for x in data if data.count(x) > 1]

示例列表解决方案时间比较

我已将 C.NivisPatrick Artner 的答案放入一个函数中,以便更轻松地在其上运行 timeit。

为了考虑调用函数所需的时间,我还将列表推导式包装成一个函数调用。

设置

def remove_singletons(data):
    """Return list with no singleton using for loops."""
    res = []
    for i in data:
        if data.count(i) > 1:
            res.append(i)
    return res

def remove_singletons_lc(data):
    """Return list with no singleton using for list comprehension."""
    return [x for x in data if data.count(x)>1]

from collections import Counter

def remove_singletons_counter(data):
     c = Counter(data)
     return [x for x in data if c[x] > 1]

import numpy as np

def remove_singletons_numpy(data):
     a = np.array(data)
     _, ids, counts = np.unique(a, return_counts=True, return_inverse=True)
    return a[counts[ids] != 1]

l = [1,2,3,2,1]

循环解决方案

%timeit remove_singletons(l)
>>> 1.42 µs ± 46.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

使用列表推导式的解决方案

%timeit remove_singletons_lc(l)
>>> 1.2 µs ± 17.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

使用Counter解决方案

%timeit remove_singletons_counter(l)
>>> 6.55 µs ± 143 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

使用numpy.unique解决方案

%timeit remove_singletons_numpy(l)
>>> 53.8 µs ± 3.07 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) 

结论

看起来列表推导式比循环略微但一致地更快,并且比使用小型列表的Counter要快得多。对于小型列表,Numpy则较慢。

大型列表的解决方案时间比较

假设我们有一个包含n个随机元素的大型列表,范围为[0, n]

import random
n = 10000
l = [random.randint(0, n) for i in range(n)]

循环解决方案

%timeit remove_singletons(l)
>>> 1.5 s ± 64.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

使用列表推导式的解决方案

%timeit remove_singletons_lc(l)
>>> 1.51 s ± 33.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

使用 Counter 实现的解决方案

%timeit remove_singletons_counter(l)
>>> 2.65 ms ± 228 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

使用numpy.unique解决方案

%timeit remove_singletons_numpy(l)
>>> 1.75 ms ± 38.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

大型列表的结论

对于大型列表,毫无争议的胜者是 numpy.unique,其次是略微落后的 Counter

最终结论

对于小型列表,列表推导 似乎很适用,但对于较大的列表,numpy.unique 方法效果最佳。


1
除非您有一个由一百万个数字组成的列表,且算法必须对每个单独的数字进行“.count()”操作并且每次都要遍历整个列表才能得到它的计数... - Patrick Artner
我可以测试! 给我5分钟 :) 我还将添加计数器方法。 - Luca Cappelletti
尽快将您的版本与测试一起添加! - Luca Cappelletti
你也可以比较计算时间 a = [random.randint(0, n) for i in range(n)]b = random.choices(range(n+1), k = n) ;o) - Patrick Artner
当然可以,但是它们没有包含在我运行的timeit中。这是结果:https://imgur.com/a/8QWL68J - Luca Cappelletti
显示剩余2条评论

1

在迭代列表时不要修改它,否则行为很可能不是所期望的。

numpy.uniquereturn_counts=True

另一个选项是使用numpy

a = np.array([1,2,2,3,2,1])
_, ids, counts = np.unique(a, return_counts=True, return_inverse=True)
a[counts[ids] != 1]

对于大型数组,这比列表推导和Counter要快得多。
a = np.array([1,2,2,3,2,1]*1000) #numpy array
b = list(a) # list

那么

%timeit _, ids, c = np.unique(a, return_counts=True, return_inverse=True);a[c[ids] != 1]
225 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit [x for x in a if b.count(x) > 1]
885 ms ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit [x for x in a if c[x] > 1]
1.53 ms ± 58.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

请记住,对列表进行乘法操作会使指向对象的指针倍增。 - Luca Cappelletti
@LucaCappelletti 什么?这与这里有什么关系? - rafaelc
比计数器更快,很好 - 我已经点了赞。为什么更快?numpy在内部使用C吗? - Patrick Artner
你说得对,在这种情况下,这并不重要,因为在我的例子中,列表的方差极大,而在你的例子中没有单例。结果在这里:https://imgur.com/a/3o3hq7R - Luca Cappelletti

0

使用del代替pop

data = [1,2,3,2,1]

for i in data:
        if data.count(i)== 1:
            index = data. index(i)
            del data[index]
            
print(data)

产生,

[1, 2, 2, 1]

[Program finished]

-1
你可以使用列表推导式来构建新的列表:
[ x for x in data if data.count(x)>1 ]

此外,pop() 方法需要将元素的索引作为参数传入,而不是元素的值。

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