迭代列表并删除重复元素时循环问题

时间:2011-08-20 11:26:19

标签: python list loops iterator

我想迭代列表,并删除多次计数的项目,因此for循环不会重复打印它们。

然而,在列表中只出现一次的一些项目似乎也受此影响,我无法弄清楚原因。

任何输入都将不胜感激。

示例输出:

listy = [2,2,1,3,4,2,1,2,3,4,5]
for i in listy:
  if listy.count(i)>1:
    print i, listy.count(i)
    while i in listy: listy.remove(i)
  else:
    print i, listy.count(i)

输出:

 2 4
 3 2
 1 2

因此完全忽略了4和5.

8 个答案:

答案 0 :(得分:5)

迭代时不应修改列表。这应该工作:

listy = [2,2,1,3,4,2,1,2,3,4,5]
found = set()
for i in listy:
    if not i in found:
        print i, listy.count(i)
        found.add(i)

结果是:

2 4
1 2
3 2
4 2
5 1

答案 1 :(得分:3)

您遇到问题的原因是您在迭代时修改了列表。

如果您不关心项目在输出中的显示顺序而不关心计数,您只需使用一组:

>>> listy = [2,2,1,3,4,2,1,2,3,4,5]
>>> print set(listy)
set([1, 2, 3, 4, 5])

如果 关心计数,请使用标准库中Counter模块的collections类:

>>> import collections
>>> collections.Counter(listy)
Counter({2: 4, 1: 2, 3: 2, 4: 2, 5: 1})
>>> c = collections.Counter(listy)
>>> for item in c.iteritems():
...     print "%i has a count of %i" % item
... 
1 has a count of 2
2 has a count of 4
3 has a count of 2
4 has a count of 2
5 has a count of 1

如果您同时关心订单和计数,则必须构建第二个列表:

>>> checked = []
>>> counts = []
>>> for item in listy: 
>>>     if item not in checked: 
>>>         checked.append(item) 
>>>         counts.append(listy.count(item))
>>> print zip(checked, counts)
... [(2, 4), (1, 2), (3, 2), (4, 2), (5, 1)]

当然,这是效率最低的解决方案。

如果您不想保留以后的计数,则不需要counts列表:

listy = [2,2,1,3,4,2,1,2,3,4,5]
checked = set()
for item in listy: 
    # "continue early" looks better when there is lots of code for
    # handling the other case
    if item in checked:     
        continue

    checked.add(item) 
    print item, listy.count(item)

答案 2 :(得分:1)

迭代时不要修改列表,每次都会弄乱你:

listy = [2,2,1,3,4,2,1,2,3,4,5]
#        *     *     * Get hit
for i in listy:
    print i
    if listy.count(i) > 1:
        print i, listy.count(i), 'item and occurences'
        while i in listy: listy.remove(i)
    else:
        print i, listy.count(i)
  1. 首先,删除四个2。两个在开头就是正确的,所以这会让你处于第一个1
  2. 然后,当您从i获得下一个listy,然后将您放在第一个3时,您就前进一个。
  3. 然后删除两个3。第一个就在那里,这样就可以让你进入第一个4
  4. 然后你再次前进一个。 2已经消失,因此这会让您进入第二个1
  5. 然后删除1个;这会让你前进两个空间。 23已消失,因此您可以访问5
  6. 你前进一个,这会让你离开列表的末尾,所以循环结束了。
  7. 如果你想要的只是打印每个项目一次,你可以使用简单的set方法,或者你可以使用itertools unique_everseen recipe

    def unique_everseen(iterable, key=None):
        "List unique elements, preserving order. Remember all elements ever seen."
        # unique_everseen('AAAABBBCCDAABBB') --> A B C D
        # unique_everseen('ABBCcAD', str.lower) --> A B C D
        seen = set()
        seen_add = seen.add
        if key is None:
            for element in ifilterfalse(seen.__contains__, iterable):
                seen_add(element)
                yield element
        else:
            for element in iterable:
                k = key(element)
                if k not in seen:
                    seen_add(k)
                    yield element
    

    扩展了基本set版本,允许您指定比较项目的特殊方式。

    如果您想知道哪些项目只在列表中一次:

    listy2 = filter(lambda i: listy.count(i) == 1, listy)
    

    listy2现在已经发生了所有单次事件。

    如果您不喜欢lambda,请执行以下操作:

    def getsingles(listy):
        def singles(i):
            return listy.count(i) == 1
        return singles
    

    然后:

    listy2 = filter(getsingles(listy), listy)
    

    这是一个特殊功能,可以告诉您哪些项目只在listy一次。

答案 3 :(得分:1)

你得到的行为的原因在于:

http://docs.python.org/reference/compound_stmts.html#index-811

更新1

出于性能原因,agf的解决方案不是很好:根据每个元素的计数过滤列表。对每个元素进行计数,也就是说,遍历整个列表进行计数的计数过程与列表中的元素一样多次完成:它过度消耗时间,想象一下如果列表是1000长度< / p>

我认为更好的解决方案是使用Counter的实例:

import random
from collections import Counter

li = [ random.randint(0,20) for i in xrange(30)]

c = Counter(li)

print c
print type(c)

res = [ k for k in c if c[k]==1]
print res

结果

Counter({8: 5, 0: 3, 4: 3, 9: 3, 2: 2, 5: 2, 11: 2, 3: 1, 6: 1, 10: 1, 12: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1})
<class 'collections.Counter'>
[3, 6, 10, 12, 15, 16, 17, 18, 19, 20]

另一个解决方案是在一个集合中添加读取元素,以便程序避免对已经看到的元素进行计数。

更新2

错误....我的解决方案是愚蠢的,你不想选择在列表中只出现一次的元素....

然后,我认为以下代码是正确的:

import random
from collections import Counter

listy = [ random.randint(0,20) for i in xrange(30)]
print 'listy==',listy
print

c = Counter(listy)
print c
print type(c)
print

slimmed_listy = []
for el in listy:
    if el in c:
        slimmed_listy.append(el)
        print 'element',el,'  count ==',c[el]
        del c[el] 
print

print 'slimmed_listy==',slimmed_listy

结果

listy== [13, 10, 1, 1, 13, 11, 18, 15, 3, 15, 12, 11, 15, 18, 11, 10, 14, 10, 20, 3, 18, 9, 11, 2, 19, 15, 5, 14, 1, 1]

Counter({1: 4, 11: 4, 15: 4, 10: 3, 18: 3, 3: 2, 13: 2, 14: 2, 2: 1, 5: 1, 9: 1, 12: 1, 19: 1, 20: 1})
<class 'collections.Counter'>

element 13   count == 2
element 10   count == 3
element 1   count == 4
element 11   count == 4
element 18   count == 3
element 15   count == 4
element 3   count == 2
element 12   count == 1
element 14   count == 2
element 20   count == 1
element 9   count == 1
element 2   count == 1
element 19   count == 1
element 5   count == 1

slimmed_listy== [13, 10, 1, 11, 18, 15, 3, 12, 14, 20, 9, 2, 19, 5]

如果您不希望结果按 listy 的顺序排列,则代码会更简单

更新3

如果您只想打印,我建议:

import random
from collections import Counter

listy = [ random.randint(0,20) for i in xrange(30)]
print 'listy==',listy
print


def gener(li):
    c = Counter(li)
    for el in li:
        if el in c:
            yield el,c[el]
            del c[el] 


print '\n'.join('element %4s   count %4s' % x for x in gener(listy))

结果

listy== [16, 2, 4, 9, 15, 19, 1, 1, 3, 5, 12, 15, 12, 3, 17, 13, 8, 11, 4, 6, 15, 1, 0, 1, 3, 3, 6, 5, 0, 8]

element   16   count    1
element    2   count    1
element    4   count    2
element    9   count    1
element   15   count    3
element   19   count    1
element    1   count    4
element    3   count    4
element    5   count    2
element   12   count    2
element   17   count    1
element   13   count    1
element    8   count    2
element   11   count    1
element    6   count    2
element    0   count    2

答案 4 :(得分:1)

在迭代它时修改列表对我遇到的每种语言都是一个坏主意。我的建议是:不要这样做。以下是一些更好的想法。

使用set查找单次出现次数

source = [2,2,1,3,4,2,1,2,3,4,5]
for s in set(source):
    print s

你明白了:

>>> source = [2,2,1,3,4,2,1,2,3,4,5]
>>> for s in set(source):
...     print s
... 
1
2
3
4
5

如果您需要计数,请使用defaultdict

from collections import defaultdict
d = defaultdict(int)
source = [2,2,1,3,4,2,1,2,3,4,5]
for s in source:
    d[s] += 1

for k, v in d.iteritems():
    print k, v

你会得到这个:

>>> for k, v in d.iteritems():
...     print k, v
... 
1 2
2 4
3 2
4 2
5 1

如果您希望对结果进行排序,请使用sortoperator

import operator
for k, v in sorted(d.iteritems(), key=operator.itemgetter(1)):
    print k, v

你会得到这个:

>>> import operator
>>> for k, v in sorted(d.iteritems(), key=operator.itemgetter(1)):
...     print k, v
... 
5 1
1 2
3 2
4 2
2 4

答案 5 :(得分:0)

我不确定迭代列表并同时删除元素是否是个好主意。如果你真的只想输出所有项目及其出现次数,我会这样做:

listy = [2,2,1,3,4,2,1,2,3,4,5]
listx = []
listc = []
for i in listy:
    if not i in listx:
        listx += [i]
        listc += [listy.count(i)]
for x, c in zip(listx, listc):
    print x, c

答案 6 :(得分:0)

就像agf所说的那样,在迭代它时修改列表会导致问题。您可以使用whilepop来解决您的代码:

single_occurrences = []
while listy:
    i = listy.pop(0)
    count = listy.count(i)+1
    if count > 1:
        print i, count
        while i in listy: listy.remove(i)
    else:
        print i, count
    single_occurrences.append(i)

输出:

2 4
1 2
3 2
4 2
5 1

答案 7 :(得分:0)

这样做的一种方法是创建一个结果列表并测试测试值是否在其中:

res=[]
listy = [2,2,1,3,4,2,1,2,3,4,5]

for i in listy:
    if listy.count(i)>1 and i not in res:
        res.append(i)

for i in res:
    print i, listy.count(i)

结果:

2 4
1 2
3 2
4 2
相关问题