0

My plan is to remove all numbers from 0 to 1000 which have fewer than two digits. I'm doing this:

numbers = range(0,1001)
#check for two or more digits
for number in numbers:
    if len(str(number)) < 2:
        numbers.remove(number)

When I then check my array, it still contains these numbers "1, 3, 5, 7, 9". Why is that?

Iban
  • 1
  • 3
  • 1
    Iirc, removing from list while iterating over it often leads to undesirable results. Why not use a filtered list comprehension? – Carcigenicate Aug 19 '17 at 15:22
  • To complete @Carcigenicate's comment: `[i for i in range(0, 1001) if len(str(i)) >= 2]` – Christian Dean Aug 19 '17 at 15:23
  • I was trying to remember the syntax to post an answer :0. Been too long since I've written one. – Carcigenicate Aug 19 '17 at 15:24
  • @Carcigenicate [Here's a nice refresher](http://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/) if your trying to get back in the game. You're gonna need it if you wanna be able to answer questions like this in the Python tag ;-) – Christian Dean Aug 19 '17 at 15:28
  • 1
    Removing items from a list that you're iterating over is generally unsafe, unless you iterate backwards. It's a lot like sawing off a tree branch that you're sitting on. ;) See [here](https://sopython.com/canon/95/removing-items-from-a-list-while-iterating-over-the-list) for details. – PM 2Ring Aug 19 '17 at 15:28
  • @ChristianDean I know how they work, the exact syntax was just escaping me. I've been using Clojure's comprehensions lately, which look very different. But ya, I definitely do need a general Python refresher. – Carcigenicate Aug 19 '17 at 15:30
  • I'm a bit surprised solutions submitted don't consider not converting to `str` and just filtering on numeric value which is much faster (by factor of nearly 7). `[i for i in range(1001) if i >= 100]` – tdube Aug 19 '17 at 15:56

4 Answers4

2

You shouldn't change a list or another sort of container while you are iterating over it. This is the sort of errors which occur. See here for more details.


More precisely, when you remove the current or earlier elements from the list, you are shifting it towards the "left". Since the loop is most likely translated to a classic for one, you'll end up skipping the elements which were shifted to the left.

Horia Coman
  • 8,681
  • 2
  • 23
  • 25
2

The iterator doesn't know about changes to the underlying list. It simply returns the next available number, which means after you remove 0, the iterator currently points to 1, and so yields 2 as the next value to check. The short answer is: don't modify a list you are iterating over.

Instead, make a new list containing the values you want to keep:

numbers = range(0, 1001)
new_numbers = []
for number in numbers:
    if len(str(number)) >= 2:
        new_numbers.append(number)

numbers = new_numbers

More simply, use a list comprehension:

numbers = [x for x in numbers if len(str(x)) >= 2]
chepner
  • 497,756
  • 71
  • 530
  • 681
1

I see two possible problems. First, it's a range, not an actual array. Second, it's a bad idea to modify a list while iterating over it. Confider using list comprehensions.

numbers = [n for n in range(0,1001) if len(str(n)) >= 2]
Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
  • 1
    Good points, however, the OP is using Python 2, where `range` returns an actual `list` object. If you try to call `.remove` on a Python 3 `range` it'll raise `AttributeError: 'range' object has no attribute 'remove'` – PM 2Ring Aug 19 '17 at 15:32
0

The for loop just generates an index to iterate the list. You are modifying the list inside the loop so it will not give the correct result.

Lets say you have a list with len = 10

numbers = [0,1,2,3,4,5,6,7,8,9]

and you run this loop

for number in numbers:
    if number < 2:
        numbers.remove(number)

in the first iteration, the index will be 0, numbers will be [0,1,2,3,4,5,6,7,8,9] and it will remove numbers[0] = 0

On the second iteration the index will be 1, and the numbers list will be [1,2,3,4,5,6,7,8,9] because we removed the first element on the last loop, it will now remove numbers[1] = 2

And so on.

Best way to do what you want is to use a list comprehesion:

numbers = [x for x in numbers if len(str(number)) > 2]
Enuff
  • 457
  • 5
  • 21