I'm ignoring the semantics of your intersect
function.
It should not matter for the purposes of your question if it is a question about loops in python.
If this is a question about your intersect
function's semantics in this particular use case, you have not provided enough information.
In general modifying an iterable object (like a list) while looping over it is dangerous and discouraged.
For example, if we write this loop
xs = [ 1 ]
for x in xs:
xs.append(x+1)
python will actually loop infinitely.
The iterator for list
objects will continue to grab the newly appended elements.
You can solve this by not modifying lst
until you are done iterating over it:
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]
Note that this solution is far from perfect.
There are two major issues I still have with it: using a list
instead of a set
for to_remove
, which better express your meaning, and repeating comparisons by doing a naieve nested loop.
The next step in improving this would be to replace the to_remove
with a set
object, and to cut down on the excessive looping.
We can do this easily enough using list slicing and the handy enumerate
function.
So, part 1 is switching over to 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]
The second component, using enumerate
, relies on knowledge of the slice notation.
If you are not familiar with it, I recommend reading up on it.
A good SO post on it: Explain Python's slice notation
Anyway, here we go:
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]
If you don't actually need lst
to be a list, you can go a step further and make it a set
as well.
That opens up the possibility of exploiting the built-in set difference operation, but that makes the looping somewhat harder.
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
EDIT:
In my initial answer, I did not remove elements from consideration after they were added to to_remove