[Tutor] Fixed freezing problem, have new one (fwd)

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Sun Jan 12 20:02:01 2003


> Danny, I want to chip in here and pick your brains about this one if I
> may :)


Ok, I'm back!


Don Arnold answered your first question, so I can skip to the next one:

> The module (or function) then initialises a control structure - the
> 'while' loop - that sets up the minimum limit as an error control - i.e.
> if the dice is not rolled, there can't be any output.

Right: if we say something like "Roll zero numbers of dice", we should get
some kind of empty output from the program.  But we want to be careful
what we mean by "empty" though: do we want to literally get the word
"empty"?

###
def dice(number, sides):
    if number <= 0:
        return "empty"
    ...
###

or should we use the None value, or something else?  It may seem a little
nitpicky, but it actually does matter if we go for a recursive definition
of dice().  We can talk about this if you'd like.



> Assuming that the while loop is initialised (i.e. number *is* > -1), the
> value is assigned from the calculation on the right side of the '='
> sign.
>
> Where does one get the 'random.randint' part from? Is that a module that
> you imported or is it a keyword that Python recognises? I can't see it
> listed as a keyword anywhere though.

Yes, 'random' is a module that's in the Standard Library.  It's
specialized for generating "pseudorandom" numbers.  There's some
documentation about the 'random' module here:

    http://www.python.org/doc/lib/module-random.html

If you read this, I'd recommend skipping down near the middle where the
docs describe the "Functions for integers"; the stuff at the top and
bottom is also very useful, but a bit specialized.



> Why then would you increment the number through 'number+1'? And, again,
> why have the value returned as 'dice(number-2, ...' - why would you
> subtract the 2?

Tthat's because the code needs a little retooling: it's doing more work
than it really needs to do.  I didn't want to just roll in refinements to
the code all at once without some kind of feedback.  Guess it's time to
charge forward!


Let's take a look at it again:

###
def dice(number,sides):
    while number > -1 :
        value = random.randint(0, sides)
        number = number + 1
        return value, dice(number - 2, sides)
###

As an intermediate step, I'm going to do something that might look a
little weird and arbitrary:  I will change that 'while' loop into an 'if'
block.

###
def dice(number,sides):
    if number > -1 :
        value = random.randint(0, sides)
        number = number + 1
        return value, dice(number - 2, sides)
###

This has the same effect as the original code because, even though the
original code used a "while loop", there was no looping involved due to
that 'return' statement at the end of the loop body.  But let's double
check that this still works.

###
>>> dice(3, 6)
(6, (0, (3, (0, None))))
###

That's a bit odd.  It's giving us FOUR dice rolls instead of three!  The
original code had the same problem because the condition we're checking,

    number > -1

is too permisive: we should really be checking if we're rolling a nonempty
number of dice:

    number > 0.


Let's make that change:

###
def dice(number, sides):
    if number > 0:
        value = random.randint(0, sides)
        number = number + 1
        return value, dice(number - 2, sides)
###

Let's try that again.

###
>>> dice(3, 6)
(6, (0, (0, None)))
>>> dice(3, 6)
(4, (4, (1, None)))
>>> dice(3, 6)
(5, (0, (3, None)))
>>> dice(3, 6)
(4, (5, (5, None)))
>>> dice(3, 6)
(6, (2, (6, None)))
>>> dice(3, 6)
(1, (1, (2, None)))
###


Ah, better.  Sorry about getting sidetracked there.  *grin*


Let's go back to your question.

> Why then would you increment the number through 'number+1'? And, again,
> why have the value returned as 'dice(number-2, ...' - why would you
> subtract the 2?

That's because it's doing too much work.  If we go one step forward, and
two steps backwards, we end up one step backward.  But how do we see this
without just stating it?  And how do we fix it?



Let's do something unusual by trying to follow an artificial programming
restriction: we won't allow "rebinding" a variable name that's already
defined, but instead we'll add a new temporary variable.  In technical
terms, we'll be "Introducing a Temporary Variable".  That is, we're not
going to do any variable name reassignments.

It's a little weird, but we'll see in a moment why this is a good idea.
What does dice() look like if we follow this restriction?

###
def dice(number, sides):
    if number > 0:
        value = random.randint(0,sides)
        next_number = number + 1
        return value, dice(next_number - 2, sides)
###


That programming rule --- no rebinding --- that we followed above isn't
actually as arbitrary as it seems.  It suddently allows us to do
"plugging-in" safely.  Whereever we see 'next_number', we can now
"plug-in" and substitute the right-hand side value of 'next-number' and
still have a program that behaves the same way:

###
def dice(number, sides):
    if number > 0:
        value = random.randint(0, sides)
        next_number = number + 1
        return value, dice((number + 1) - 2, sides)
###


Once we've done this, we can just drop the initial assignment to
'next_number', since 'next_number' is a useless variable that's not being
used anywhere anymore.


###
def dice(number, sides):
    if number > 0:
        value = random.randint(0,sides)
        return value, dice(number + 1 - 2, sides)
###

I don't think I need to outline the next step.  *grin* But let's see what
dice() looks like now:

###
def dice(number, sides):
    if number > 0:
        value = random.randint(0, sides)
        return value, dice(number - 1, sides)
###

Does this revision make more sense?



Please feel free to ask more questions about this; I know I went fast on
that one, but it's an exciting topic: if we're careful about reassignment,
we can do controlled manipulation of our programs to clean them up.  It's
almost like factoring math equations to make them easier to understand.