我有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
为空。为什么会发生这种情况,我该如何解决?如果有另一种方法,它对我有用,而不是我现在拥有的。
答案 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