[Tutor] Strange modulus problem

Kirby Urner urnerk@qwest.net
Thu, 11 Oct 2001 20:22:57 -0700


>
>Why doesn't '5' fail the if test and get removed? If I modify
>the code so that it prints the modulus of each item, 5 correctly 
>evalutates to '1'. What gives?
>
>- Chris

This has to do with iterating over the object you're
also modifying.

The code below works because it iterates over a copy
of factor_range (the effect of putting [:] after it):

   >>> def factors(n):
           factor_range = range(1, n + 1)
           for item in factor_range[:]:
               if n % item:
                  factor_range.remove(item)
           return factor_range

   >>> factors(6)
   [1, 2, 3, 6]
   >>> factors(20)
   [1, 2, 4, 5, 10, 20]

(Note also that n%item is true if non-zero, so you
don't really need the additional >0).

In the your way of doing it, you don't actually get to
test all the numbers in the range, because you yank an
offender out of the line-up, shortening the list, so
when the loop comes around and it iterates to the
"next" item, it's hitting against a foreshortened list,
meaning it skips over a value you wish it hadn't.

You can study this behavior by inserting a line that
prints the item being tested:

   >>> def factors(n):
           factor_range = range(1, n + 1)
           for item in factor_range:  # not a copy
               print item  # debug print
               if n % item:
                  factor_range.remove(item)
           return factor_range

    >>> factors(10)
    1
    2
    3
    5
    6
    8
    10
    [1, 2, 4, 5, 7, 9, 10]

You see that 4 wasn't tested, because the program yanks
3 (not a factor), and so the next candidate it hits if 5.
Likewise, 7 and 9 sneak through.

This is a common programming error in Python and you're
lucky to stumble across it in such an illuminating
manner.  It's very good to make mistakes of this kind
-- very text book.  You have a promising career as a
Pythoneer ahead of you (pardon the fortune cookie).

Kirby