Python 2.7在循环时从列表中删除项目

时间:2015-02-26 19:24:51

标签: python algorithm list python-2.7 loops

我有list object ffscore属性和inter(f)(交叉)方法。我想要一个非交叉f对象的列表,如果是交叉点,我会删除一个得分较低的对象。

我尝试通过两个for循环来解决此问题,并为除了我要删除的项目之外的所有项目创建一个临时tmp列表,然后我将tmp放入原始列表中(lst)我已经在工作了。

for f1 in lst: 
    for f2 in lst: 
        if f1!=f2:
            if f1.intersect(f2):
                if f1.score>=f2.score:
                    tmp=[f for f in lst if f!=f2]
                    lst=[]
                    lst.extend(tmp)
                else:
                    tmp=[f for f in lst if f!=f1]
                    lst=[]
                    lst.extend(tmp)    

问题:有时它会起作用,但有时最后的lst为空。为什么会发生这种情况,我该如何解决?如果有另一种方法,它对我有用,而不是我现在拥有的。

1 个答案:

答案 0 :(得分:2)

我忽略了intersect函数的语义。 如果它是关于python循环的问题,那么对于你的问题的目的应该没关系。 如果这是关于此特定用例中intersect函数语义的问题,则表示您没有提供足够的信息。


一般来说,修改一个可迭代对象(如列表)而循环它是危险的,不鼓励。 例如,如果我们写这个循环

xs = [ 1 ]
for x in xs:
    xs.append(x+1)

python实际上会无限循环。 list对象的迭代器将继续抓取新附加的元素。

你可以通过不修改lst来解决这个问题,直到你完成迭代:

to_remove = []
for f1 in lst:
    # because lst is not being modified, we have to manually skip
    # elements which we will remove later
    # the performance difference is negligible on small lists
    if f1 in to_remove:
        continue
    for f2 in lst:
        # also skip f2s which we removed
        if f2 in to_remove:
            continue
        # note that I collapsed two of your conditions here for brevity
        # this is functionally the same as what you wrote, but looks neater
        if f1 != f2 and f1.intersect(f2):
            if f1.score >= f2.score:
                to_remove.append(f2)
            else:
                to_remove.append(f1)
lst = [x for x in lst if x not in to_remove]

请注意,此解决方案远非完美。 我还有两个主要问题:使用list代替set代替to_remove,更好地表达您的意思,并通过执行naieve嵌套循环重复比较。< / p>

改进此功能的下一步是将to_remove替换为set对象,并减少过多的循环。 我们可以使用列表切片和方便的enumerate函数轻松地完成此操作。

因此,第1部分正在切换到set s:

to_remove = set()
for f1 in lst:
    if f1 in to_remove:
        continue
    for f2 in lst:
        if f2 in to_remove:
            continue
        if f1 != f2 and f1.intersect(f2):
            if f1.score >= f2.score:
                to_remove.add(f2)
            else:
                to_remove.add(f1)
lst = [x for x in lst if x not in to_remove]

使用enumerate的第二个组件依赖于切片表示法的知识。 如果您不熟悉它,我建议您阅读它。 一篇好的SO帖子:Explain Python's slice notation

无论如何,我们走了:

to_remove = set()
# with enumerate, we walk over index, element pairs
for index,f1 in enumerate(lst):
    if f1 in to_remove:
        continue
    # parens in slicing aren't required, but add clarity
    for f2 in lst[(index+1):]:
        if f2 in to_remove:
            continue
        # no need to check for f1 == f2, since that's now impossible
        # unless elements are duplicated in your list, which I assume
        # is not the case
        if f1.intersect(f2):
            if f1.score >= f2.score:
                to_remove.add(f2)
            else:
                to_remove.add(f1)
# still probably the clearest/easiest way of trimming lst
lst = [x for x in lst if x not in to_remove]

如果您实际上不需要lst作为列表,则可以更进一步,并将其设为set。 这开启了利用内置集差异操作的可能性,但这使得循环变得更加困难。

to_remove = set()
# still iterate over it as a list, since we need that to be able to slice it
# if you replace it with a set at the outset, you can always listify it
# by doing `list(lst_as_set)`
for index,f1 in enumerate(lst):
    if f1 in to_remove:
        continue
    # parens in slicing aren't required, but add clarity
    for f2 in lst[(index+1):]:
        if f2 in to_remove:
            continue
        # no need to check for f1 == f2, since that's now impossible
        if f1.intersect(f2):
            if f1.score >= f2.score:
                to_remove.add(f2)
            else:
                to_remove.add(f1)

# yep, we can turn the set into a list more or less trivially
# (usually, duplicate elements make things complicated)
keep = set(lst)
# set difference can be done with the minus sign:
# https://docs.python.org/2/library/stdtypes.html#set
keep = keep - to_remove

编辑: 在我最初的回答中,我没有将元素添加到to_remove

后删除