在列表中查找非唯一元素不起作用

7

我想在列表中找到非唯一的元素,但是我无法弄清楚为什么在以下代码部分中没有发生这种情况。

>>> d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]
>>> for i in d:
...     if d.count(i) == 1:
...             d.remove(i)
... 
>>> d
[1, 2, 1, 2, 4, 4, 'a', 'b', 'a', 'b', 6, 3]

6和3应该被移除。 然而,如果我使用


d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c']

我得到了正确的答案。请解释一下发生了什么,我感到困惑!!!
我正在使用Python 2.7.5。
8个回答

26

在迭代列表时删除其元素从未是一个好的想法。正确的做法是使用 collections.Counter列表推导式

>>> from collections import Counter
>>> d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6, 'f', 3]
>>> # Use items() instead of iteritems() in Python 3
>>> [k for (k,v) in Counter(d).iteritems() if v > 1]
['a', 1, 2, 'b', 4]

如果你希望保留列表中出现的重复元素的顺序:

>>> keep = {k for (k,v) in Counter(d).iteritems() if v > 1}
>>> [x for x in d if x in keep]
[1, 2, 1, 2, 4, 4, 'a', 'b', 'a', 'b']

我会尝试解释为什么你的方法不起作用。为了理解某些元素未按预期删除的原因,想象一下我们要在循环列表[a,b,b,c]时删除所有的bs。它看起来像这样:

+-----------------------+
|  a  |  b  |  b  |  c  |
+-----------------------+
   ^(第一次迭代)
+-----------------------+ | a | b | b | c | +-----------------------+ ^(下一个迭代:我们找到了一个“b”--删除它)
+-----------------------+ | a | | b | c | +-----------------------+ ^(已删除b)
+-----------------+ | a | b | c | +-----------------+ ^(将随后的元素向下移动以填补空缺)
+-----------------+ | a | b | c | +-----------------+ ^(下一个迭代)

注意,我们跳过了第二个b!一旦我们删除了第一个b,元素就被向下移动,我们的for循环因此未能触及列表的每个元素。在您的代码中发生了同样的事情。



使用计数器它是有效的,但为什么那段代码不起作用。我想知道其中的问题在哪里?! - Tanmaya Meher
顺便说一下,我希望答案是[1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b']而不是['a',1,2,'b',4]。如果我在你的代码中使用Counter的elements()方法来获取这个答案,那么列表将会被排序,而我不想要这种情况! - Tanmaya Meher
1
@tanmay 看一下编辑(顺便问一下,5 是笔误吗?为什么要在那个列表中加入 5?)。 - arshajii
谢谢你的解释,至少我现在知道发生了什么!!:) - Tanmaya Meher
抱歉,5 不会在列表中!!这是我的复制错误!!答案应该是 [1,2,1,2,4,4,'a','b','a','b'],即包括它们的重复和顺序的所有重复或不唯一的元素。 - Tanmaya Meher
5
使用Python 3.5.3,建议的解决方案会返回一个错误:“AttributeError: 'Counter' object has no attribute 'iteritems'”。我使用了items()替换了iteritems(),这样对我来说很好用。请注意,这不会改变原意。 - gplssm

4

3

如果有人感兴趣,我觉得我可以分享一下我的集合推导式方法。

>>> d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]
>>> d = list({x for x in d if d.count(x) > 1})
>>> print d
['a', 1, 2, 'b', 4]

我相信Python 2.7及以上版本支持集合推导式功能。

1
请不要使用这个方法,它的时间复杂度为O(n^2)。 - diralik

2

感谢所有的答案和评论!

思考一会儿后,我在之前编写的代码中得到了另一个答案。因此,我发布它。

d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]
e = d[:] # just a bit of trick/spice
>>> for i in d:
...     if d.count(i) == 1:
...             e.remove(i)
... 
>>> e
[1, 2, 1, 2, 4, 4, 'a', 'b', 'a', 'b']

@arshajii,您的解释让我想到了这个技巧。谢谢!


1
您也可以这样做:

data=[1,2,3,4,1,2,3,1,2,1,5,6]
    first_list=[]
    second_list=[]
    for i in data:
        if data.count(i)==1:
            first_list.append(i)
        else:
            second_list.append(i)
            print (second_list)

结果

[1, 2, 3, 1, 2, 3, 1, 2, 1]


0
在Python3中,使用dict.items()代替dict.iteritems() iteritems()在Python3中已被移除,因此您不能再使用这个方法了。
    >>> d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]
    >>> from collections import Counter
    >>> [k for k, v in Counter(d).items() if v > 1]
    ['a', 1, 2, 'b', 4]

0

对于

>>> d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]

使用转换为集合可以得到唯一的项:

>>> d_unique = list(set(d))

使用列表推导式可以找到非唯一的项

>>> [item for item in d_unique if d.count(item) >1]
[1, 2, 4, 'a', 'b']

0
要同时获取键值对(key, value),请使用dict而不是list推导式:
from collections import Counter


d = [1, 2, 1, 2, 4, 4, 5, 'a', 'b', 'a', 'b', 'c', 6,'f',3]
{k: v for k, v in Counter(d).items() if v > 1}

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