【问题标题】:Loop problem while iterating through a list and removing recurring elements [duplicate]遍历列表并删除重复元素时出现循环问题[重复]
【发布时间】:2011-10-31 04:20:39
【问题描述】:

我想遍历一个列表,并删除多次计数的项目,这样它们就不会被 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。

【问题讨论】:

  • 您是只想打印,还是同时精简列表?
  • 您希望打印的顺序与 listy 中的元素顺序相同吗?

标签: python list loops iterator


【解决方案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. 首先,删除四个2s。有两个在开头,所以你在第一个 1
  2. 然后,当您从listy 获得下一个i 时,您将前进一个,将您置于第一个3
  3. 然后删除两个3s。第一个就在那里,因此您将位于第一个 4
  4. 然后你再次前进。 2 已经消失了,所以这会将您置于第二个 1
  5. 然后你删除两个1s;这会使您向前移动两个空格。 23 已消失,因此您将转到 5
  6. 您前进一个,这会将您移出列表末尾,因此循环结束。

如果您只想打印每个项目一次,您可以使用简单的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 中只有一次。

【讨论】:

  • -1 从他的示例和文本中可以清楚地看出,这不是 OP 想要的。他想循环列表并将每个元素打印一次。
  • 对不起,我还没有开始学习 lambda 函数。你知道如何在不使用 lambda 的情况下做到这一点吗?我运行了它,但我不确定它是否在做我想要它做的事情。霍华德有想法,没必要-1,我敢肯定他误解了这个问题
  • @agf 我明白,但请更改“你想要...”部分,我很高兴撤消 -1。
  • @Howard @Louis93 查看我的编辑。它为listyfilter 方法提供了更多选项。
  • 啊,我误会了。我以为他只想要单个元素,其余的都是找到它们的副作用。更新了我的答案。
【解决方案2】:

您不应该在迭代列表时修改它。这个应该可以工作:

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

【讨论】:

  • 您的解决方案比我的要好得多。 :) 我没有想到这个集合,如果元素已经在那里,你可以即时测试......
  • 我可以用列表代替集合吗?为什么首选集合?
  • 是的,你可以。但是套装更快。这对于如此小的列表并不重要,但如果您的数据增长。
  • 您的解决方案是:“保留一组看到的数字,如果不在集合中,则打印。”您也可以从列表中构建集合并打印集合。 for x in set(listy): print x
  • 我想重申一下hughdbrown所说的话。 set() 解决方案是一个很好的解决方案,但它基本上应该是单行的。
【解决方案3】:

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

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

【讨论】:

  • 如果你打算这样做,你最好使用set
  • 我知道。这就是为什么我 +1 了另一个答案。
  • 同意@hop,如果你真的这样做,就使用append
【解决方案4】:

就像 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

【讨论】:

  • 不过,当你完成后,这并没有给你留下一个单次出现的列表。我不知道这是否重要。
  • 不,这完全清除了列表,就像 Louis93 的原始代码一样。我也不知道他是否希望保存单个事件。
  • ?他的原始代码仅从计数 > 1 的列表中删除项目。
  • 哦,你是对的!我的错,现在更正我的代码!
【解决方案5】:

你得到的行为的原因在这里,在注释中:

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

更新 1

出于性能原因,agf 的解决方案不是一个好的解决方案:根据每个元素的计数过滤列表。对每个元素进行计数,也就是说,包括遍历整个列表进行计数的计数过程与列表中的元素一样多:这是非常耗时的,想象一下如果你的列表是 1000 长度

我认为更好的解决方案是使用 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

errrr....我的解决方案很愚蠢,您不想选择仅在列表中出现一次的元素....

那么我认为下面的代码是正确的:

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

【讨论】:

  • @agf 你提到了什么设置方法?我没有看到使用 Counter() 的方法与不使用它的另一种方法相同。我在某处读到 Counter() 的实例是一个优化的工具:只运行一次它比一个接一个地计算一个元素的出现次数要好(如果它是你提到的带有 set 的方法)
  • @agf 谢谢。所以它在Achim的答案中。我问是因为在你的 unique_everseen 食谱、hop 的答案和 hughdbrown 的答案中也使用了一套;以及使用列表而不是其他答案中的集合的相同方法。但是所有这些方法都必须与 set 的使用分开计算,而在我的代码中,它是同一个 Counter 的实例,它计算并使其元素一个接一个地逐渐删除:不需要两个不同的对象。这就是为什么我的算法(我不认为更好)似乎与基于集合的算法不太一样。
  • @agf 我有点怀疑:您假装您最初提出了仅打印代码,也就是说,正是霍华德指责您没有?在您回答后查看他的第一条评论:“-1 从他的示例和文本中可以清楚地看出这不是 OP 想要的。他想循环列表并将每个元素打印一次。”以及为什么在霍华德的第一个评论和他的第二个评论之间消失了您的评论:“@agf 我明白,但请更改“你想要...”部分,我很高兴撤消 -1。” ?不过,我记得在他们之间读过你的评论
  • @agf 英语不是我的母语,有时我会混淆含义。 “假装”对于法国人来说是一个令人困惑的词。 “prétendre”在法语中的意思是“你这么说,但我很难相信”,而不是肯定的。我没有使用“假装,使相信”的意思的“假装”,因为我不确定,而是“声称”的意思。但是我对你的话有一种奇怪的感觉。
  • @agf 如您所知,您经常删除 cmets 的事实无助于消除不确定的理解,原因对我来说似乎很可疑。你的意思是我和你的cmets属于“讨论”类别吗?顺便说一句,当评论从线程中消失时,它也会从历史记录中消失。
【解决方案6】:

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

如果您不关心项目在输出中出现的顺序并且不关心计数,您可以简单地使用使用集合:

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

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

>>> 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)

【讨论】:

    【解决方案7】:

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

    使用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
    

    【讨论】:

      【解决方案8】:

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

      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
      

      【讨论】:

        猜你喜欢
        • 2021-03-15
        • 2010-11-24
        • 2021-07-31
        • 2011-11-30
        • 2017-12-20
        • 2019-10-16
        • 2017-02-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多