James Sapam
James Sapam

Reputation: 16940

Difference between normal looping of list and using slice?

In the following two examples, I am trying to remove negative element from a list using two different types of looping.

First I tried by using normal looping for i in list but in this case when i do list1.remove(elm) the size of the list is reduce by one element. So, in the next loop the second element moves to the first element position. So, the second element is missed from testing if elm < 0 in the next loop. So, it doesn't remove all the negative element from the list.

Secondly, I tried using slicing. What i understood from slicing is it creates a temporary list. So, when i do for i in list2[:] it creates a new temporary list2 = [-1,3,-2,-5,4,7,8] but still I didn't get a clear picture, how it works. It removes all the negative element.

#!/usr/env/bin
# remove all the negative value from the list.
list1 = [-1,3,-2,-5,4,7,8]
list2 = [-1,3,-2,-5,4,7,8]

# Looping through the list.
for elm in list1:
    if elm < 0:
        list1.remove(elm)
print 'list1: ', list1

# Looping through the list using slice.
for elm in list2[:]:
    if elm < 0:
        list2.remove(elm)
print 'list2: ', list2

Output:-python slice.py
list1: [3, -5, 4, 7, 8]
list2: [3, 4, 7, 8]

Upvotes: 2

Views: 389

Answers (4)

Paul Draper
Paul Draper

Reputation: 83235

The problem is that iterating through a list and modifying it at the same time can produce some bad results.

For example, if you are at index 2, and remove a negative number there, the value at index 3 will move to index 2. On the next iteration, you will go to index 3, but skip the value at the old index 3 (new index 2).

The slice creates a copy, and so it is untouched, even as you remove negatives.

FYI, another possibility is

filter(lambda x: x >= 0, list2)

or

[x for x in list2 if x >= 0]

EDIT:

Here are the iterations.

(index)        list1            elem
   0      [-1,3,-2,-5,4,7,8]     -1
   1      [3,-2,-5,4,7,8]        -2
   2      [3,-5,4,7,8]            4
   3      [3,4,7,8]               8

Do you see how we missed several values because we were iterating and modifying the same thing?

Now with the slice, which creates a copy,

(index)        (copy)             list2            elem
   0     [-1,3,-2,-5,4,7,8]  [-1,3,-2,-5,4,7,8]     -1
   1     [-1,3,-2,-5,4,7,8]  [3,-2,-5,4,7,8]         3
   2     [-1,3,-2,-5,4,7,8]  [3,-2,-5,4,7,8]        -2
   3     [-1,3,-2,-5,4,7,8]  [3,-5,4,7,8]           -5
   4     [-1,3,-2,-5,4,7,8]  [3,4,7,8]               4
   5     [-1,3,-2,-5,4,7,8]  [3,4,7,8]               7
   6     [-1,3,-2,-5,4,7,8]  [3,4,7,8]               8

We iterate through all seven values, without skipping any.

FYI, list2[:] is equivalent to list2[0:len(list2):1] or list(list2).

Upvotes: 4

kiriloff
kiriloff

Reputation: 26333

slicing operator copies the list you are operating on, leading to accurate result.

Upvotes: 0

TerryA
TerryA

Reputation: 59974

In list one, python is removing an item while you're iterating through it, which can lead to unexpected results, as you have seen. Watch:

for elm in list1:
    if elm < 0:
        list1.remove(elm)
    print list1

This will print:

[3, -2, -5, 4, 7, 8]
[3, -5, 4, 7, 8]
[3, -5, 4, 7, 8]
[3, -5, 4, 7, 8]
[3, -5, 4, 7, 8]

So when you python goes back to for elm in list1 after removing an item, it will not go to the one after that, but the one before (3). Hence the loop.

When you make a copy, list2[:], you're not actually iterating over list2 but just a copy, hence when you remove items, you're not removing anything what you're iterating over.

Upvotes: 1

kindall
kindall

Reputation: 184131

I'd go with a list comprehension here to either create a new list containing the elements you want to keep:

numbers = [-1, 3, -2, -5, 4, 7, 8]
positive = [n for n in numbers if n >= 0]

or to reassign the new sequence into the existing one via slice assignment:

numbers = [-1, 3, -2, -5, 4, 7, 8]
numbers[:] = (n for n in numbers if n >= 0)

Upvotes: 1

Related Questions