statements in control structures (Re: Conditional Expressions don't solve the problem)

Paul Winkler slinkp23 at yahoo.com
Thu Oct 25 03:18:31 EDT 2001


On Thu, 25 Oct 2001 01:17:31 +0000 (UTC), Huaiyu Zhu
<huaiyu at gauss.almadan.ibm.com> wrote:

>On Wed, 24 Oct 2001 20:11:31 GMT, William Tanksley
><wtanksle at dolphin.openprojects.net> wrote: 
>>
>>I'm confused -- I don't see how your version can work either.  Both of you
>>are iterating over an empty list, and the only code which could possibly
>>add anything to the list is inside the iteration; that list is empty no
>>matter what, so nothing will execute, and the list will never be anything
>>but empty.
>
>That's what that 'else' is for - and that's the whole point of this thread.
>I have a suspicion that the usage of for-else in Python is not well known to
>many discussants in this thread.
>
>The equivalent code is (note the duplication of primes.append(n)):
>    
>    def getprimes(x):
>        primes = []
>        for n in range(2, x):
>            for p in primes:
>                if p*p > n:         primes.append(n); break
>                elif n % p == 0:    break
>            else:           primes.append(n)
>        return primes
>
>Try it and see how it works.
>    
>
>>I don't like your "for x in list while y" construct, either.  It's
>>terrificly ambiguous: does it mean that every item in the list which meets
>>the 'y' criterion will be processed, or does it loop over all the items up
>>to the first one which fails to satisfy 'y'?
>
>So let me explain this in detail and hope I don't need to do it again.  The
>pseudo code was:
>    
>    def getprimes(x):
>        primes = []
>        for n in range(2, x):
>            for p in primes while p*p < n:
>                if n % p == 0: break
>            else: primes.append(n)
>        return primes
>    
>The 'for p' loop will exit under three conditions:
>- p reaches end of primes: jump to (A)
>- not p*p < n: jump to (A)
>- n % p == 0: jump to (B)
>
>where 
>
>(A) primes.append(n)
>(B) pass
>
>
>The juicy part of the code is:
>    
>            for p in primes while p*p < n:
>                if n % p == 0: break
>            else: primes.append(n)

>
>Note that the two different conditions corresponding to (A) are both
>considered intuitively as "exhausted all the candidate factors".
>
>So the question is: is there a way to write the idea that two of the exits
>go to the same follow-up code (A) without actually duplicating the code in
>(A), or using a temporary variable to hold the state information?  I have
>not found a positive answer (which is not the same as claiming it does not
>exist).

In this particular case, there is, because the two exits to (A) can be
combined into one test. I posted something like this to the other
recent thread on primes.

def getPrimesTill(x):
    # Based somewhat on primes.py from python cvs
    if x <=2: return []
    primes = [2]
    for n in xrange(3, x, 2):
        for p in primes:
            if n % p == 0 or p * p > i: break
        if n % p != 0: primes.append(i)
    return primes

However, this probably won't satisfy you for two reasons:

1) It's a special case - we have a known value we can use to
initialize the list, and add code before the loop to handle that.

2) There's still code duplication. Instead of duplicating the
primes.append(n) call, we're duplicating a comparison.

>Of course, all of these could be easily done by using an auxiliary variable
>found_factor which is set to true or false before different breaks, and
>using an "if found_factor" after the loop.

This has the advantage of generalizing to loops that exit under many
possible conditions and have more than two possible results of those
conditions - in other words, found_factor is not boolean.

The problem with the syntax you described is simply that the
conditions when it is useful are rather restrictive:

1) There must be more than one condition to exit the loop.
2) There must be *exactly* two possible behaviors upon loop exit.

I suspect that #2 is likely to be a pretty fragile requirement for
whatever you're testing.

An example. Goldilocks wants to try some porridge.
First a simple case that works well for your proposal.

def taste1(porridges):
    """Taste porridges, and stop if just right,
    too hot, or no more."""
    for heat in porridges while heat < 10:
        if heat == 5:
            print "Just right"
            break
    else: print "I don't want to try any more"

Compare that with the boolean variable version you don't like:

def taste2(porridges):
    """Taste porridges, and stop if just right, too hot, or no more."""
    for heat in porridges:
        just_right = 0
        if heat == 5:
            just_right = 1
            break
        elif heat >= 10:
            break
    if just_right: print "Just right"
    else: print "I don't want to try any more"


By comparison, it seems perhaps clunky.

But now requirements have changed and Goldilocks needs to be more
sensitive (and talkative)... and return the temperature of the last
tasted bowl (or None if there are none).  taste2 can be made to do all
this just by adding to it:

def taste3(porridges):
    print "Oh look! Porridge! Hope there are no bears here."
    for heat in porridges:
        print "Slurp slurp...",
        if heat < 0:
            print "Brrr! My tongue is numb, I can't taste anything else."
            break
        elif heat < 5:
            print "This porridge is too cold."
        elif heat == 5:
            print 'MMMM! Yummy.'
            break
        elif heat > 10:
            print "Ow! Burned my tongue. I'd better stop."
            break
        elif heat > 5:   # or just 'else:'
            print "This porridge is too hot."

    else: 
        print 'Rats. Nothing tasty here.'

    if porridge: return heat
    else: return None


But can we keep it in your idiom and add all this functionality?
Let's try.

def taste3(porridges):
    print "Oh look! Porridge! Hope there are no bears here."
    for heat in porridges while 0 <= heat <= 10:
        print "Slurp slurp...",
        if heat < 5:
            print "This porridge is too cold."
        elif heat > 5:
            print "This porridge is too hot."
        else:
            print 'MMMM! Yummy.'
            break
    else:
        # Does this work???
        if heat < 0:
            print "Brrr! My tongue is numb, I can't taste anything else."
        elif heat > 10:
            print "Ow! Burned my tongue. I'd better stop."
	else: print 'Rats. Nothing tasty here.'
    if porridge: return heat
    else: return None
        

I don't care much for that (assuming it even works).  We have to test
for the boundary conditions twice: once in the 'while' conditions,
once after the loop exits.

That's probably more than enough blather out of me on this subject.

-- Paul Winkler

            








More information about the Python-list mailing list