mro wrote:
> Here's one for you all. Print it out and take some time for mind crunching
> fun. Admittedly, it took me quite some time to figure out.
>
> There are 5 houses in five different colors. In each house lives a person
> with a different nationality. These five owners drink a certain drink, smoke
> a certain brand of cigar, and keep a certain pet. No owners have the same
> pet, smoke the same brand of cigar or drink the same drink. The question is
> --- who owns the fish?
>
> Hints:
> - the Brit lives in the red house
> - the Swede keeps dogs as pets
> - the Dane drinks tea
> - the green house is on the left of the white house
> - the green house owner drinks coffee
> - the person who smokes Pall Mal rears birds
> - the owner of the yellow house smokes Dunhill
> - the man living in the house right in the center drinks milk
> - the Norwegian lives in the first house
> - the man who smokes Blends lives next to the one who keeps cats
> - the man who keeps horses lives next to the one who smokes Dunhill
> - the owner who smokes Blue Master drinks beer
> - the German smokes Prince
> - the Norwegian lives next to the blue house
> - the man who smokes Blend has a neighbor who drinks water
> *** Einstein wrote this quiz last century. He said that 98% of the World
> could not figure it out. ***
Aagh! That is too hard for me. I had to ask my computer to help.
(A solver, written in Python, is attached. It requires Python 2.2.)
--
Bob Miller K<bob>
kbobsoft software consulting
http://kbobsoft.com [EMAIL PROTECTED]
# There are 5 houses in five different colors. In each house lives a person
# with a different nationality. These five owners drink a certain drink, smoke
# a certain brand of cigar, and keep a certain pet. No owners have the same
# pet, smoke the same brand of cigar or drink the same drink. The question is
# --- who owns the fish?
#
# Hints:
# R1 - the Brit lives in the red house
# R2 - the Swede keeps dogs as pets
# R3 - the Dane drinks tea
# R4 - the green house is on the left of the white house
# R5 - the green house owner drinks coffee
# R6 - the person who smokes Pall Mall rears birds
# R7 - the owner of the yellow house smokes Dunhill
# R8 - the man living in the house right in the center drinks milk
# R9 - the Norwegian lives in the first house
# R10 - the man who smokes Blends lives next to the one who keeps cats
# R11 - the man who keeps horses lives next to the one who smokes Dunhill
# R12 - the owner who smokes Blue Master drinks beer
# R13 - the German smokes Prince
# R14 - the Norwegian lives next to the blue house
# R15 - the man who smokes Blend has a neighbor who drinks water
# *** Einstein wrote this quiz last century. He said that 98% of the World
# could not figure it out. ***
########################################################################
#
# Some terminology.
#
# There are five "persons". Each person has five traits.
#
# There are five "traits". The traits are house_color,
# nationality, drink, smoke, and pet.
#
# Each trait has five "alternatives". For example,
# the alternatives for drink are beer, coffee, milk, tea, and water.
#
# There are fifteen "rules" which restrict which persons have
# which alternatives. For example, Rule 1 is, "The Brit lives in
# the red house."
#
# A "solution" is a permutation of alternatives among persons.
# It is implemented as a list of five persons, and each person
# is implemented as a list of five alternatives.
from __future__ import generators
def enumerate(seq): # built in in Python 2.3
"""Generate (index, element) for all elements in a sequence."""
i = 0
it = iter(seq)
while 1:
yield i, it.next()
i += 1
class Alternative(object):
def __init__(self, name, trait):
self.name = name
self.trait = trait
def get_trait(self):
return self.trait
def __repr__(self):
return self.name
class Trait(int):
"""Trait is derived from int so that traits can be used to index lists."""
count = 0
def _make(_class, name, alternatives):
"""Make a new trait."""
t = _class(_class.count)
_class.count += 1
t.init(name, alternatives)
# Put the trait name in the global namespace.
exec('global %s; %s = t' % (name, name))
return t
make = classmethod(_make)
def init(self, name, alternatives):
"""Initialize a trait."""
self.name = name
self.rules = []
self.alternatives = []
for altname in alternatives.split(' '):
alt = Alternative(altname, self)
self.alternatives.append(alt)
# Put each alternative in the global namespace.
exec('global %s; %s = alt' % (altname, altname))
def __repr__(self):
return self.name
def add_rule(self, rule):
self.rules.append(rule)
def apply_rules(self, solution):
"Apply this trait's rules to a solution. Returns true "
"if all rules test okay."
return reduce(lambda p, r: p and r.apply(solution),
self.rules, True)
number_of_persons = 5
traits = [
Trait.make('house_color', 'blue green red white yellow'),
Trait.make('nationality', 'Briton Dane German Norwegian Swede'),
Trait.make('drink', 'beer coffee milk tea water'),
Trait.make('smoke', 'Blend BlueMaster Dunhill PallMall Prince'),
Trait.make('pet', 'birds cats dogs fish horses')
]
number_of_traits = len(traits)
if 0:
print traits, [int(t) for t in traits]
print pet.alternatives
print fish
import sys
sys.exit()
class Rule(object):
def __init__(self):
self.applications = 0
def apply(self, solution):
self.applications += 1
self.solution = solution
r = self.test()
assert r == True or r == False
return r
def test(self):
raise NotImplemented
def same_person_is(self, alt1, alt2):
t1 = alt1.get_trait()
for person in self.solution:
if person[t1] == alt1:
return person[alt2.get_trait()] == alt2
return None # fail
def person_is_left_of(self, alt1, alt2):
t2 = alt2.get_trait()
for i, man in enumerate(self.solution):
if man[t2] == alt2:
return i > 0 and self.solution[i-1][alt1.get_trait()] == alt1
return None # fail
def persons_are_adjacent(self, alt1, alt2):
return self.person_is_left_of(alt1, alt2) or \
self.person_is_left_of(alt2, alt1)
class Rule1(Rule):
def test(self):
"""The Brit lives in the red house."""
return self.same_person_is(Briton, red)
class Rule2(Rule):
def test(self):
"""The Swede keeps dogs as pets."""
return self.same_person_is(Swede, dogs)
class Rule3(Rule):
def test(self):
"""The Dane drinks tea."""
return self.same_person_is(Dane, tea)
class Rule4(Rule):
def test(self):
"""The green house is on the left of the white house."""
return self.person_is_left_of(green, white)
class Rule5(Rule):
def test(self):
"""The green house owner drinks coffee."""
return self.same_person_is(green, coffee)
class Rule6(Rule):
def test(self):
"""The person who smokes Pall Mall rears birds."""
return self.same_person_is(PallMall, birds)
class Rule7(Rule):
def test(self):
"""The owner of the yellow house smokes Dunhill."""
return self.same_person_is(yellow, Dunhill)
class Rule8(Rule):
def test(self):
"""The man living in the house right in the center drinks milk."""
return self.solution[2][drink] == milk
class Rule9(Rule):
def test(self):
"""The Norwegian lives in the first house."""
return self.solution[0][nationality] == Norwegian
class Rule10(Rule):
def test(self):
"""The man who smokes Blends lives next to the one who keeps cats."""
return self.persons_are_adjacent(Blend, cats)
class Rule11(Rule):
def test(self):
"""The man who keeps horses lives next to the one who smokes Dunhill"""
return self.persons_are_adjacent(horses, Dunhill)
class Rule12(Rule):
def test(self):
"""The owner who smokes Blue Master drinks beer."""
return self.same_person_is(BlueMaster, beer)
class Rule13(Rule):
def test(self):
"""The German smokes Prince."""
return self.same_person_is(German, Prince)
class Rule14(Rule):
def test(self):
"""The Norwegian lives next to the blue house."""
return self.persons_are_adjacent(Norwegian, blue)
class Rule15(Rule):
def test(self):
"""The man who smokes Blend has a neighbor who drinks water."""
return self.persons_are_adjacent(Blend, water)
rules = [Rule1(), Rule2(), Rule3(), Rule4(), Rule5(),
Rule6(), Rule7(), Rule8(), Rule9(), Rule10(),
Rule11(), Rule12(), Rule13(), Rule14(), Rule15()]
# This is the important bit. As we try solutions, we try permuting
# each trait in turn. So that we can reject bad solutions early on,
# and skip large parts of the solution space, we attach each rule to
# the "last" trait that rule depends on. By "last", we mean last in
# the traits list, which means it's filled out last by
# generate_solutions.
#
# We get ugly and reach inside each rule's code block to find out what
# traits/alternatives the rule references.
for rule in rules:
last_trait = None
for trait in traits:
for thing in [trait] + trait.alternatives:
if thing.name in rule.test.func_code.co_names:
last_trait = trait
last_trait.add_rule(rule)
def gen_permutations(n):
"""Generate all permutations of range(n)."""
def find_ascending(seq):
"""Find rightmost place where seq is in ascending order."""
i = len(seq) - 2
while i >= 0:
if seq[i] < seq[i+1]:
return i
i -= 1
def min_more(v, seq, start):
"""Find smallest item in seq[start:] greater than v."""
k = start
for j in range(start + 1, n):
if seq[j] > v and seq[j] < seq[k]:
k = j
return k
seq = range(n)
while 1:
yield seq
i = find_ascending(seq)
if i is None:
break
k = min_more(seq[i], seq, i + 1)
tail = seq[i:k] + seq[k+1:]
tail.sort()
seq = seq[:i] + seq[k:k+1] + tail
permutations = [p for p in gen_permutations(number_of_persons)]
def print_solution(solution):
for i, person in enumerate(solution):
print '%d: %s' % (i, ' '.join(['%-10s' % str(a) for a in person]))
print
def generate_solutions(traitindex, solution):
"""Generate all solutions that meet the rules."""
n = number_of_traits
if traitindex == n:
yield solution
return
trait = traits[traitindex]
av = trait.alternatives
for perm in permutations:
for i in range(number_of_persons):
solution[i][traitindex] = av[perm[i]]
if trait.apply_rules(solution):
for sol in generate_solutions(traitindex + 1, solution):
yield sol
def null_solution():
"""Return the null solution.
Has the right shape, but None for all values. """
return [[None] * number_of_traits for i in range(number_of_persons)]
def doit():
"""Solve the puzzle."""
for sol in generate_solutions(0, null_solution()):
print_solution(sol)
if 0:
for i, rule in enumerate(rules):
print 'applied rule %d %d times' % (i, rule.applications)
if __name__ == '__main__':
doit()