0

[Notice: Even use a temporary array (not modifying loop array), the code didn't give the right answer for list b.]

Remove the odd numbers from a list, see below for codes. Why it works for list a, not list b? Thank you.

def purify(numbers):
    numbers_buf = numbers
    for item in numbers:
        if item % 2 == 1:
            numbers_buf.remove(item)
    return numbers_buf

a = [1,2,3,4]
print purify(a)
[2, 4]

b = [4,5,5,4]
print purify(b)
[4,5,4]
Kai
  • 165
  • 1
  • 2
  • 11

6 Answers6

2
numbers_buf = numbers[:] #  make a copy not a reference


def purify(numbers):
    numbers_buf = numbers[:]
    for item in numbers:
        if item % 2:
            numbers_buf.remove(item)
    return numbers_buf

manipulating/removing elements while you iterate over a list will change the index of elements, numbers_buf = numbers creates a reference to the numbers list, basically a pointer to the same object so remove from one, remove from both.

Using numbers[:] creates a copy/new object.

Run the following function to help make it clear, see how 3 and 4 somehow did a runner:

def purify(numbers):
    numbers_buf = numbers
    should_be = numbers[:]
    for ind, item in enumerate(numbers):
        print("{} should be at {}, is at {}".format(item,should_be.index(item),ind))
        if item % 2:
            numbers_buf.remove(item)
    return numbers_buf
print(purify([4,5,4,7,3,1]))

if you did want to use a list comp to modify the original object:

def purify(numbers):
    numbers[:] = [x for x in numbers if not x % 2]
    return numbers

You can also use reversed:

def purify(numbers):
    for item in reversed(numbers):
        if item % 2:
            numbers.remove(item)
    return numbers
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • Even if using `numbers_buf = numbers`, which was a reference (not a copy), given list `a = [1,2,3,4]`, the function `purify` still returns the right answer. Could you please comment on how this happen? – Kai Nov 09 '14 at 01:46
  • 1
    the consecutive elements `5,5` are how you are getting caught using the reference, – Padraic Cunningham Nov 09 '14 at 01:52
  • OK. If I gave two consecutive odd numbers, the function failed. However, if there was no consecutive odd numbers, the function would return right answer. This is the part confuse me. – Kai Nov 09 '14 at 02:06
  • 1
    basically because when you start iterating over the elements, iter has pointers to all the elements so it knows where to look for the next, if you modify the list iter has no way of knowing so you can end up skipping some elements as you are here. `[4,5,4,7,3,1]` will also not work not just consecutive numbers. – Padraic Cunningham Nov 09 '14 at 02:14
  • A better option may be to make a new list and append items you wish to keep, as done here https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating. – Josh Herzberg Sep 04 '19 at 02:04
1

This has been asked many times, look at Modifying list while iterating

You should really be doing:

even = [a for a in numbers if a%2 == 0]
Community
  • 1
  • 1
igon
  • 3,016
  • 1
  • 22
  • 37
  • 1
    This is a good solution but it's important to note that this doesn't have *quite* the same functionality as the OP's original `purify`, which actually modifies its input argument. Though maybe not intentionally, or necessarily. – jez Nov 09 '14 at 01:14
1

You are doing two things that you probably should not, as both of them are asking for trouble.

  1. Never, ever, ever add to or remove items from an array while you're iterating over it. This will confuse both Python and you and almost certainly not achieve the desired result.

  2. You really shouldn't be modifying the array in place anyway. The function doesn't get a copy of the array; it's modifying the actual array passed in by the caller. This is likely to be surprising behavior to everyone involved:

    a = [1,2,3,4]
    print purify(a)        #=> [2, 4]
    print a                #=> [2, 4]
    

You should construct and return a new array instead, and perhaps change the function name to indicate that (similar to the sort and sorted built-ins):

def purified(a):
  return [n for n in a if n % 2 == 0]

a = [1,2,3,4]
print purified(a)      #=> [2, 4]
print a                #=> [1, 2, 3, 4]
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

Your problem stems from the fact that you are not looping over a different list, like you want to be. You want to avoid deleting something from a list while looping over it, which is why you created a new list with numbers_buf = numbers

However, note that no new list was created:

In [73]: numbers = list(range(10))

In [74]: numbers
Out[74]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [75]: id(numbers)
Out[75]: 4469310792

In [76]: numbers_buf = numbers

In [77]: id(numbers)
Out[77]: 4469310792

In [78]: id(numbers_buf)
Out[78]: 4469310792

In [79]: id(numbers_buf) == id(numbers)
Out[79]: True

In [80]: numbers
Out[80]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [81]: numbers_buf
Out[81]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [82]: numbers.append(10)

In [83]: numbers_buf
Out[83]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Notice in the end, when I append 10 to numbers, it gets appended to numbers_buf as well. This is because python doesn't create a new list with numbers in them when you do numbers_buf = numbers; It simply creates the name numbers_buf and makes it point to the exact same structure that numbers points to (this is what all that id(numbers) stuff shows). You could solve this problem as follows:

In [84]: numbers_buf_new = numbers[:]

In [85]: id(numbers_buf_new)
Out[85]: 4469329032

In [86]: id(numbers_buf_new) == id(numbers)
Out[86]: False

In [87]: numbers
Out[87]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [88]: numbers_buf_new
Out[88]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [89]: numbers.append(11)

In [90]: numbers
Out[90]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

In [91]: numbers_buf_new
Out[91]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
0

If you want to retain using remove method and return the same list, you can try this:

def purify(numbers):

for item in numbers:
    if item % 2 == 1:
        count = numbers.count(item)
        for i in range(count):
            numbers.remove(item)
return numbers
sifoo
  • 799
  • 5
  • 3
0

Your function is working in another way than you would expect. The for loop takes first element, than second etc., so when you remove one element, others change their positions and can be skipped by it (and that happens in your case) when they are preceded by another odd number.

you can use this code instead:

def purify(numbers):
return [x for x in numbers if x % 2 == 0]   

it will return [2, 4] [4, 4] for a and b respectively

user 12321
  • 2,846
  • 1
  • 24
  • 34