If that was second-best, what is first?

Contents

A better answer

Peter Norvig stated that giving the c function attributes for c.starts and c.items was his "second-best" solution. He shares his preferred solution here.

Notice that c(orderings) has to be used in 5 different places with the second-best (function attribute-based) solution, while c is used in only one place with the better (class-based) solution.

class c(object):
    """Convert a sequence to an iterable that counts accesses to it.
    At any time you can ask the object for .starts (the number of iters
    that have started) and .items (the total number of items iterated)."""

    def __init__(self, sequence):
        self.sequence = list(sequence)
        self.starts, self.items = 0, 0

    def __iter__(self):
        self.starts += 1
        for item in self.sequence:
            self.items += 1
            yield item

houses = first, _, middle, _, _ = [1, 2, 3, 4, 5]
orderings = c(itertools.permutations(houses))
def zebra_puzzle():
    "Return a tuple (WATER, ZEBRA indicating their house numbers."
    return next((WATER, ZEBRA)
                for (red, green, ivory, yellow, blue) in orderings
                if imright(green, ivory)
                for (Englishman, Spaniard, Ukranian, Japanese, Norwegian) in orderings
                if Englishman is red
                if Norwegian is first
                if nextto(Norwegian, blue)
                for (coffee, tea, milk, oj, WATER) in orderings
                if coffee is green
                if Ukranian is tea
                if milk is middle
                for (OldGold, Kools, Chesterfields, LuckyStrike, Parliaments) in orderings
                if Kools is yellow
                if LuckyStrike is oj
                if Japanese is Parliaments
                for (dog, snails, fox, horse, ZEBRA) in orderings
                if Spaniard is dog
                if OldGold is snails
                if nextto(Chesterfields, fox)
                if nextto(Kools, horse)
                )

def instrument_fn(fn, *args):
    result = fn(*args)
    print('%s got %s with %5d iters over %7d items'%(
        fn.__name__, result, orderings.starts, orderings.items))

The second-best answer

def zebra_puzzle():
    "Return a tuple (WATER, ZEBRA indicating their house numbers."
    houses = first, _, middle, _, _ = [1, 2, 3, 4, 5]
    orderings = list(itertools.permutations(houses)) # 1
    return next((WATER, ZEBRA)
                for (red, green, ivory, yellow, blue) in c(orderings)
                if imright(green, ivory)
                for (Englishman, Spaniard, Ukranian, Japanese, Norwegian) in c(orderings)
                if Englishman is red
                if Norwegian is first
                if nextto(Norwegian, blue)
                for (coffee, tea, milk, oj, WATER) in c(orderings)
                if coffee is green
                if Ukranian is tea
                if milk is middle
                for (OldGold, Kools, Chesterfields, LuckyStrike, Parliaments) in c(orderings)
                if Kools is yellow
                if LuckyStrike is oj
                if Japanese is Parliaments
                for (dog, snails, fox, horse, ZEBRA) in c(orderings)
                if Spaniard is dog
                if OldGold is snails
                if nextto(Chesterfields, fox)
                if nextto(Kools, horse)
                )

def c(sequence):
    c.starts += 1
    for item in sequence:
        c.items += 1
        yield item

def instrument_fn(fn, *args):
    c.starts, c.items = 0, 0
    result = fn(*args)
    print('%s got %s with %5d iters over %7d items'%(
        fn.__name__, result, c.starts, c.items))