Search code examples
pythonpython-itertools

How to update a list during a combination?


I would like to compare the elements of a list, and at the end of each for loop the combination resumes with the new updated list.

from itertools import combinations

aListe = ['a', 'b', 'c', 'd']
for first, second in combinations(aListe, 2):
    # do something
    aListe.remove(first)
ValueError: list.remove(x): x not in list

more specific example of the # do something.

Imagine my list contains shapely polygon such as

aListe = [polygon1, polygon2, polygon3, polygon4]
for first, second in combinations(aListe, 2):
    if area(first) > area(second):
        aListe.remove(first)

if the area of the first polygon in the list is already greater than the second I don't want it to be compared to the others. I would like the next for loop start on an updated list without the first polygon.


Solution

  • As you have seen, your code failed because itertools.combinations visits each list item multiple times and it can only be removed once (assuming that it is only contained once).

    But even if you had added a check like if first in aList to circumvent this error, you might have gotten unexpected results, as is generally the case when removing items from a list while iterating over it, see Removing from a list while iterating over it.

    In your case, instead of actually removing a polygon from the list, I would mark it as "removed" by adding it to a "removed" set, and skip an iteration if an already removed polygon is encountered again.

    (Note, in order to add polygons to a set, if I understand https://shapely.readthedocs.io/en/stable/manual.html correctly, "[…] use the geometry ids as keys since the shapely geometries themselves are not hashable.")

    from itertools import combinations
    
    removed = set()
    aListe = [polygon1, polygon2, polygon3, polygon4]
    for first, second in combinations(aListe, 2):
        if id(first) in removed:
            continue
        if area(first) > area(second):
            removed.add(id(first))