【问题标题】:Python 2.7 delete items form list while loopingPython 2.7 在循环时从列表中删除项目
【发布时间】:2015-02-26 19:24:50
【问题描述】:

我有一个list 和一个object f,而f 有一个score 属性和一个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 为空。为什么会发生这种情况,我该如何解决?如果有另一种方法,而不是我目前拥有的方法,它对我有用。

【问题讨论】:

  • “有时有效”是指在完全相同的条件/环境下以相同的输入运行完全相同的代码会随机成功/失败吗?这样的事情通常是线程问题,但不适用于 Python...
  • 什么定义了f 对象上的交集,相交对可以在两个列表中的任何位置吗? (如在不同的指数中)。可能因为使用了错误的数据结构,所以设置起来很困难。
  • @Basic 我确定她的意思是“取决于手头两个列表的内容”
  • @Two-BitAlchemist 很有可能,但在这种情况下,提供一个失败的例子和一个成功的例子将是有用的,如果不是必需的......
  • 如果一个对象与两个或多个其他对象相交会发生什么?

标签: python algorithm list python-2.7 loops


【解决方案1】:

我忽略了您的 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,这样可以更好地表达您的意思,以及通过执行一个简单的嵌套循环来重复比较。

改进这一点的下一步是将to_remove 替换为set 对象,并减少过度循环。 我们可以使用列表切片和方便的enumerate 函数轻松地做到这一点。

所以,第 1 部分正在切换到 sets:

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 后,我没有将它们从考虑中删除

【讨论】:

  • 这个答案的问题是,一旦元素被标记为删除,您实际上并没有从考虑中删除它们。假设 f1 与 f2 相交, f2 与 f3 相交,但 f1 与 f3 不相交,并且还假设 f2 的得分低于 f1,f3 的得分低于 f2。所以我们找到相交对 (f1, f2) 并标记 f2 以进行删除。但我们实际上并没有删除它,所以稍后我们找到相交对 (f2, f3),并将 f3 标记为删除。但即使 f1 和 f3 不相交,也只会在输出中留下 f1。
  • @rici 你是完全正确的。我已经修好了。感谢您了解这一点。
猜你喜欢
  • 2012-01-08
  • 1970-01-01
  • 2015-10-12
  • 2022-12-20
  • 2011-11-05
  • 1970-01-01
  • 2019-04-04
  • 1970-01-01
相关资源
最近更新 更多