[Tutor] MAXIMUM LOOP LOGIC

eryksun eryksun at gmail.com
Thu Oct 4 22:17:47 CEST 2012


On Wed, Oct 3, 2012 at 12:11 AM, medusa <magicwizardstar at gmail.com> wrote:
>
> After all the data has been read the program looks through the dictionary
> using a maximum loop
>
> Instead of printing off a number beside the email, i got another email and i
> dont know how to fix it.
> <http://python.6.n6.nabble.com/file/n4990842/9.4_stuck.png>

A dict iterates over its keys, so your loop is finding the 'max' email
address (in string sort order), which is the one starting with the
letter 'z'.

You need to iterate over iteritems() if you want both keys and values:

    for item in messages.iteritems():

"item" is a tuple containing the (key, value) pair. You can
immediately unpack this tuple if you want:

    for email, count in messages.iteritems():

BTW, your variable named "max" is masking the built-in function "max"
that will do what you want, faster and simpler -- especially since
you're testing if "max is None" for every value in the loop.

In general for a reducing operation such as max (or min, sum, etc),
initialize to the first item of the iterable/iterator. You can get the
first item of an iterator using the built-in next() function. This
function has an optional 2nd argument to set a default value, which
could be used as a sentry, but let's use a simple test to see if
"messages" is empty instead of worrying about default values and
exception handling:

    if messages:
        it = messages.iteritems()
        max_item = next(it)
        for item in it:
            if item[1] > max_item[1]:
                max_item = item
        print max_item[0], max_item[1]
    else:
        print "no messages"


There's also the built-in function "reduce" for this kind of thing.
Here's a simple example of how it works:

    >>> from operator import add
    >>> add(1, 1)
    2
    >>> reduce(add, [1,1,1,1,1])
    5

Here's what this does:

    add(1, 1) => 2
        add(2, 1) => 3
            add(3, 1) => 4
                add(4, 1) => 5


So you could replace the reducing loop with the built-in reduce()
function. First you have to define a simple function (call it "imax")
to return the max of two key/value tuple items based on the value in
index 1:

    imax = lambda a, b: b if b[1] > a[1] else a

    if messages:
        max_item = reduce(imax, messages.iteritems())
        print max_item[0], max_item[1]
    else:
        print "no messages"


Finally, here's a solution using "max". It's similar, but more efficient:

    from operator import itemgetter

    if messages:
        max_item = max(messages.iteritems(), key=itemgetter(1))
        print max_item[0], max_item[1]
    else:
        print "no messages"


dict.get also has a default value option, which will help to simplify
the code in your first for loop. Instead of testing if email is in
messages and special casing the first insertion, you can use
get(email, 0):

        messages[email] = messages.get(email, 0) + 1

Also, using a defaultdict would be even simpler and faster:

    >>> from collections import defaultdict
    >>> messages = defaultdict(int)

    >>> messages['magic at gmail.com'] += 1
    >>> messages
    defaultdict(<type 'int'>, {'magic at gmail.com': 1})


More information about the Tutor mailing list