marlon
marlon

Reputation: 7633

How to remove element from a list while iterating?

my_list = ['aa', 'aab', 'aaa', 'deff', 'abcde']

my_list is sorted by element length. What I want to do:

for each element from left to right, if an element is part of another element, then remove the shorter element. For example, since 'aa' is part of 'aab', I want to remove 'aa'.

length = len(my_list)
for i in range(0, length):
    v = my_list[i]
    j = i+1
    if j<length:
        if v in my_list[j]:
            my_list.pop(i)

This loop changed the original list so it breaks.

This version seem working:

length = len(my_list)
new_list = my_list.copy()
for i in range(0, length):
    v = my_list[i]
    for j in range(i+1, length):
        if v in my_list[j]:
            new_list.pop(i)

Upvotes: 2

Views: 597

Answers (3)

Maruf Hossain
Maruf Hossain

Reputation: 39

my_list = ['aa', 'aab', 'aaa', 'deff', 'abcde']
list_str = str(my_list) # "['aa', 'aab', 'aaa', 'deff', 'abcde']"
new_list = my_list.copy()
next_ptr = 1  # for the beginning "["
for i in range(len(my_list)-1):
    next_ptr += 4 + len(my_list[i]) # 4 is for two single quotation('), one comma(,) and one space
    if my_list[i] in list_str[next_ptr:]:
        new_list.pop(i)
my_list = new_list

Upvotes: 0

alani
alani

Reputation: 13079

You should not modify a list that you are iterating over, which you are effectively doing here. So you would create a new list my_list_out = my_list[:] and modify that instead of inside the loop.

Some other bits of unsolicited advice about your code (other than suggesting any clever one-liners to solve this):

length = len(my_list)
for i in range(0, length):
    v = my_list[i]
    j = i+1
    if j<length:
        if v in my_list[j]:
            my_list.pop(i)
  • the indentation on the for statement needs fixing

  • with the enumerate function, you can replace this sort of construct:

        for i in range(0, length):
            v = my_list[i]

with this:

        for i, v in enumerate(my_list[:length]):

or in this case, because you set length = len(my_list), just:

        for i, v in enumerate(my_list):`
  • you are not really doing anything on the last iteration, so you might as well loop up to the previous one and then avoid the if j<length test

Putting these together, plus also the fact that (as discussed in comments below) the requirement is to pop the element where the string is part of any later element and not only the immediately adjacent one, gives:

my_list = ['aa', 'aab', 'aaa', 'deff', 'abcde']

my_list_out = my_list[:]
length = len(my_list)
for i, v in enumerate(my_list[ : length-1]):
    for v2 in my_list[i+1 :]:
        if v in v2:
            my_list_out.pop(i)
            break
my_list = my_list_out

print(my_list)

In this case, in the inner loop the index is not required, so the loop is simply over the values (for v2 in ...).

Running this gives:

$ python3 test.py
['aab', 'aaa', 'deff', 'abcde']

Upvotes: 2

Valdi_Bo
Valdi_Bo

Reputation: 30971

It is easier when you process the input list in the reversed order and the reverse the result.

Use the following code (printouts for demonstration only):

out = []
for it in reversed(my_list):
    if any(it in s for s in out):
        print(f'{it} - drop')
    else:
        out.append(it)
        print(f'{it} - keep')
out = list(reversed(out))
print(out)

Upvotes: 2

Related Questions