2

Problem:

test1 = [1, 2, 3]
for i in range(len(test1)): #Want to do the below for every element in test1 
    print(test1[i])
    if test1[i] % 2 == 0: #if the element meets certain conditions, it gets deleted
        del test1[i] 
        #The thing in test1 gets deleted, but the range gets not updated,
        #and i iterates nonetheless, so 1 element gets ignored,
        #and in the end, I get "list index out of range"

(In the actual code, there are far more if statements, and the things in the array are not integers but objects?! of a class, so list comprehension is more - meh...)

I've come up with this approach:

test1 = [1, 2, 3]
i = 0
x = len(test1)
while i < x:
    print(test1[i])
    if test1[i] % 2 == 0:
        del test1[i]
        x -= 1
        i -= 1
    i += 1

But as you can see, this is 5 lines longer (and for every del, I have to add 2 lines...), ugly, and annoying. Is there any nicer way to do something like that? Like, a redo command, that goes straight to the for loop, updates the range, and lets you chose i, or something other?

2
  • 1
    Why not just do something like test2 = [i for i in test1 if i % 2 != 0]? Commented Apr 6, 2015 at 19:02
  • in general, you should expect complicated code if you want to simultaneously read and write the same data. Commented Apr 6, 2015 at 19:06

4 Answers 4

6

Use a list comprehension

Something along the lines of:

list = [x for x in list if x % 2 == 1] # Keep anything that is not even
for thing in list:
    print(thing)

This will create a new list with anything that matches the conditional. Then print the new list

>>> test1 = [1,2,3]
>>> list = [x for x in test1 if x % 2 is 1]
>>> print(list)
[1, 3]
Sign up to request clarification or add additional context in comments.

2 Comments

Or, equivalently, use list = filter(lambda x: x % 2 == 1, list).
lambda, not lamda.
3

Have you tried using the reversed on the range?

test1 = [1, 2, 3]
for i in reversed(range(len(test1))):
    print(test1[i])
    if test1[i] % 2 == 0:
        del test1[i]
>>> 3
>>> 2
>>> 1

print(test1)
>>> [1, 3]

This way, when you delete an element, the array get reduced but since you are going through the list in a reversed order, only the index of already processed elements get affected.

EDIT

After reading all the comments, I decided to run some little benchmark. I created a list of 1,000 elements and a list of 10,000,000 elements and tried the 3 ways:

  • #1 Deleting from list
  • #2 Creating a new list with list comprehension
  • #3 Creating a new list with classic for

For the list of 1,000 elements, the time does not really matter but for the 10,000,000 elements list, deleting from the list get pretty much impossible (a couple of hours vs half a second to create a new list).

Test with 1,000 element

>>> Test 0, 11th element: 7e-06 seconds
>>> Test 0, second last element: 2e-06 seconds
>>> Test 1: 0.00017 seconds
>>> Test 2: 0.000103 seconds
>>> Test 3: 0.000234 seconds

Test with 10,000,000 element

>>> Test 0, 11th element: 0.011888 seconds
>>> Test 0, second last element: 4e-06 seconds
>>> Test 1: Too long!!
>>> Test 2: 0.941158 seconds
>>> Test 3: 0.681262 seconds

In regards of this, I would strongly suggest creating a new list with the list comprehension or with a regular for loop.

Here is my code:

from datetime import datetime
from random import randint


# Create my test lists of 10 000 000 elements
test_0 = []
#nb_el = 10000000
nb_el = 1000
while (len(test_0) < nb_el):
    test_0.append(randint(0, 100))
test_1 = test_0[:] # clone list1
test_2 = test_0[:] # clone list1
test_3 = test_0[:] # clone list1


# Test #0
# Remove the 11th element and second last element
d1 = datetime.now()
del test_0[10]
d2 = datetime.now()
print('Test 0, 11th element: ' +
    str((d2-d1).microseconds / 1000000.0) + ' seconds')

d1 = datetime.now()
del test_0[nb_el-2]
d2 = datetime.now()
print('Test 0, second last element: '
    + str((d2-d1).microseconds / 1000000.0) + ' seconds')


# Test #1 | TOO SLOW TO BE RAN with 10 000 000 elements
# Delete every element where element is a multiple of 2
d1 = datetime.now()
for i in reversed(range(len(test_1))):
    if test_1[i] % 2 == 0:
        del test_1[i]
d2 = datetime.now()
print('Test 1: ' + str((d2-d1).microseconds / 1000000.0) + ' seconds')

# Test #2
# Create a new list with every element multiple of 2
# using list comprehension
d1 = datetime.now()
test_2_new = [x for x in test_2 if x % 2 == 0]
d2 = datetime.now()
print('Test 2: ' + str((d2-d1).microseconds / 1000000.0) + ' seconds')

# Test #3
# Create a new list with every element multiple of 2
# using for loop
d1 = datetime.now()
test_3_new = []
for i in range(len(test_3)):
    if test_3[i] % 2 == 0:
        test_3_new.append(test_3[i])
d2 = datetime.now()
print('Test 3: ' + str((d2-d1).microseconds / 1000000.0) + ' seconds')

5 Comments

You should also consider using some of the other approaches here. This code might solve your problem but is by far not the most efficient.
@encomiastical: Warning: while this works, it works extremely slowly if a lot of list elements need to be shifted for those dels. It is not a good idea to get into the habit of doing things this way.
@user2973474 So, what would you say is the most efficient one? For now, however, I will definitly use this, because I was dumb enough to code everything before testing, and so i just have to add reversed() to the for loops
Create a new list (with append() or a comprehension or something) instead of modifying the one you're iterating over. Modifying your iterable during iteration can lead to lots of problems, as you saw. Also, iterating over an iterable's items is generally preferable to iterating over indices, when possible.
@encomiastical, if you have a huge list, I must agree that using 2 lists and picking only the elements you need is the best approach but if you have about 1000 elements it does not really matter. See my edited answer.
3

Assuming you want to print every number before removing them from the list.

myList = [1,2,3]

for n in test1:
    print n

test1 = [n for n in myList if n % 2] # 0 = False by default in python. No need for == 0 / > 0.

if you absulutely want to use the code you wrote as it may be easier for you to understand:

test1 = [1, 2, 3]
for i in range(len(test1)): #Want to do the below for every element in test1 
    print(test1[i])


    # Either check if the index is not greather than the length of the list.

    if test1[i] % 2 == 0 and i <= len(test1): #if the element meets certain conditions, it gets deleted

    # Or use try, except (Will be slow if your list is great in size as there will be many exceptions caught.

    if test1[i] % 2 == 0:
        try:
            del test1[i] 
        except:
            continue
        #The thing in test1 gets deleted, but the range gets not updated,
        #and i iterates nonetheless, so 1 element gets ignored,
        #and in the end, I get "list index out of range"

Comments

1

Modifying an iterable while you're iterating over it can get you into lots of trouble. Try the following instead:

test1 = [1,2,3]
test2 = []
for i in test1:
    print(i)
    if i%2:
        test2.append(i)

Then you have test2 as the finished version.

This could be written more compactly with a comprehension:

test1 = [1,2,3]
test2 = [i for i in test1 if i%2]
print(*test2, sep='\n')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.