2016 Update

This commit is contained in:
Tina Müller 2016-12-05 22:15:40 +01:00
parent 948b86eafa
commit dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions

View file

@ -1,3 +1,5 @@
[[File:zebra.png|550px||right]]
The [[wp:Zebra puzzle|Zebra puzzle]], a.k.a. Einstein's Riddle,
is a logic puzzle which is to be solved programmatically. <br>
It has several variants, one of them this:
@ -19,9 +21,13 @@ It has several variants, one of them this:
#The Norwegian lives next to the blue house.
#They drink water in a house next to the house where they smoke Blend.
The question is, who owns the zebra?
<br>The question is, who owns the zebra?
Additionally, list the solution for all the houses.
Optionally, show the solution is unique.
cf. [[Dinesman's multiple-dwelling problem]], [[Twelve statements]]
;Related tasks:
* &nbsp; [[Dinesman's multiple-dwelling problem]]
* &nbsp; [[Twelve statements]]
<br><br>

View file

@ -1,8 +1,7 @@
(ns zebra
(:require [clojure.math.combinatorics :as c]))
(defn solve
[]
(defn solve []
(let [arrangements (c/permutations (range 5))
before? #(= (inc %1) %2)
after? #(= (dec %1) %2)
@ -35,8 +34,7 @@
(map zipmap [persons colors drinks cigs pets])))))
(defn -main
[& _]
(defn -main [& _]
(doseq [[[persons _ _ _ pets :as solution] i]
(map vector (solve) (iterate inc 1))
:let [zebra-house (some #(when (= :zebra (val %)) (key %)) pets)]]

View file

@ -0,0 +1,74 @@
defmodule ZebraPuzzle do
defp adjacent?(n,i,g,e) do
Enum.any?(0..3, fn x ->
(Enum.at(n,x)==i and Enum.at(g,x+1)==e) or (Enum.at(n,x+1)==i and Enum.at(g,x)==e)
end)
end
defp leftof?(n,i,g,e) do
Enum.any?(0..3, fn x -> Enum.at(n,x)==i and Enum.at(g,x+1)==e end)
end
defp coincident?(n,i,g,e) do
Enum.with_index(n) |> Enum.any?(fn {x,idx} -> x==i and Enum.at(g,idx)==e end)
end
def solve(content) do
colours = permutation(content[:Colour])
pets = permutation(content[:Pet])
drinks = permutation(content[:Drink])
smokes = permutation(content[:Smoke])
Enum.each(permutation(content[:Nationality]), fn nation ->
if hd(nation) == :Norwegian, do: # 10
Enum.each(colours, fn colour ->
if leftof?(colour, :Green, colour, :White) and # 5
coincident?(nation, :English, colour, :Red) and # 2
adjacent?(nation, :Norwegian, colour, :Blue), do: # 15
Enum.each(pets, fn pet ->
if coincident?(nation, :Swedish, pet, :Dog), do: # 3
Enum.each(drinks, fn drink ->
if Enum.at(drink,2) == :Milk and # 9
coincident?(nation, :Danish, drink, :Tea) and # 4
coincident?(colour, :Green, drink, :Coffee), do: # 6
Enum.each(smokes, fn smoke ->
if coincident?(smoke, :PallMall, pet, :Birds) and # 7
coincident?(smoke, :Dunhill, colour, :Yellow) and # 8
coincident?(smoke, :BlueMaster, drink, :Beer) and # 13
coincident?(smoke, :Prince, nation, :German) and # 14
adjacent?(smoke, :Blend, pet, :Cats) and # 11
adjacent?(smoke, :Blend, drink, :Water) and # 16
adjacent?(smoke, :Dunhill, pet, :Horse), do: # 12
print_out(content, transpose([nation, colour, pet, drink, smoke]))
end)end)end)end)end)
end
defp permutation([]), do: [[]]
defp permutation(list) do
for x <- list, y <- permutation(list -- [x]), do: [x|y]
end
defp transpose(lists) do
List.zip(lists) |> Enum.map(&Tuple.to_list/1)
end
defp print_out(content, result) do
width = for {k,v}<-content, do: Enum.map([k|v], &length(to_char_list &1)) |> Enum.max
fmt = Enum.map_join(width, " ", fn w -> "~-#{w}s" end) <> "~n"
nation = Enum.find(result, fn x -> :Zebra in x end) |> hd
IO.puts "The Zebra is owned by the man who is #{nation}\n"
:io.format fmt, Keyword.keys(content)
:io.format fmt, Enum.map(width, fn w -> String.duplicate("-", w) end)
fmt2 = String.replace(fmt, "s", "w", global: false)
Enum.with_index(result)
|> Enum.each(fn {x,i} -> :io.format fmt2, [i+1 | x] end)
end
end
content = [ House: '',
Nationality: ~w[English Swedish Danish Norwegian German]a,
Colour: ~w[Red Green White Blue Yellow]a,
Pet: ~w[Dog Birds Cats Horse Zebra]a,
Drink: ~w[Tea Coffee Milk Beer Water]a,
Smoke: ~w[PallMall Dunhill BlueMaster Prince Blend]a ]
ZebraPuzzle.solve(content)

View file

@ -1,28 +1,32 @@
% populate domain by selecting from it
attrs(H,[N-V|R]):- memberchk( N-X, H), X=V, % unique attribute names
(R=[] -> true ; attrs(H,R)).
one_of(HS,AS) :- member(H,HS), attrs(H,AS).
two_of(HS,G,AS):- call(G,H1,H2,HS), maplist(attrs,[H1,H2],AS).
left_of(A,B,HS):- append(_,[A,B|_],HS).
next_to(A,B,HS):- left_of(A,B,HS) ; left_of(B,A,HS).
zebra( Z, HS):-
length( HS, 5),
member( H1, HS), nation(H1, eng), color(H1, red),
member( H2, HS), nation(H2, swe), owns( H2, dog),
member( H3, HS), nation(H3, dane), drink(H3, tea),
left_of(A,B, HS), color( A, green), color( B, white),
member( H4, HS), drink( H4, coffee), color(H4, green),
member( H5, HS), smoke( H5, palmal), owns( H5, birds),
member( H6, HS), color( H6, yellow), smoke(H6, dunhill),
middle( C, HS), drink( C, milk),
first( D, HS), nation( D, norweg),
next_to(E,F, HS), smoke( E, blend), owns( F, cats),
next_to(G,H, HS), owns( G, horse), smoke( H, dunhill),
member( H7, HS), smoke( H7, bluemas), drink(H7, beer),
member( H8, HS), nation(H8, german), smoke(H8, prince),
next_to(I,J, HS), nation( I, norweg), color( J, blue),
next_to(V,W, HS), drink( W, water), smoke( V, blend),
member( X, HS), owns( X, zebra), nation(X, Z).
zebra(Zebra,Houses):-
Houses = [A,_,C,_,_], % 1
maplist( one_of(Houses), [ [ nation-englishman, color-red ] % 2
, [ nation-swede, owns -dog ] % 3
, [ nation-dane, drink-tea ] % 4
, [ drink -coffee, color-green ] % 6
, [ smoke -'Pall Mall', owns -birds ] % 7
, [ color -yellow, smoke-'Dunhill' ] % 8
, [ drink -beer, smoke-'Blue Master'] % 13
, [ nation-german, smoke-'Prince' ] % 14
] ),
two_of(Houses, left_of, [[color -green ], [color -white ]]), % 5
maplist(attrs, [C,A], [[drink -milk ], [nation-norwegian]]), % 9, 10
maplist(two_of(Houses,next_to),
[ [[smoke -'Blend' ], [owns -cats ]] % 11
, [[owns -horse ], [smoke-'Dunhill' ]] % 12
, [[nation-norwegian], [color-blue ]] % 15
, [[drink -water ], [smoke-'Blend' ]] % 16
] ),
one_of(Houses, [ owns-zebra, nation-Zebra]).
left_of( A, B, HS):- append( _, [A,B|_], HS).
next_to( A, B, HS):- left_of( A, B, HS) ; left_of( B, A, HS).
middle( A, [_,_,A,_,_]).
first( A, [A|_]).
attr( House, Name-Value):-
memberchk( Name-X, House), % unique attribute names
X = Value. % set, validate, or reject
nation(H, V):- attr( H, nation-V).
owns( H, V):- attr( H, owns-V). % select an attribute
smoke( H, V):- attr( H, smoke-V). % from an extensible record
color( H, V):- attr( H, color-V). % of house attributes
drink( H, V):- attr( H, drink-V). % which *is* a house

View file

@ -1,12 +1,32 @@
?- time(( zebra(Z,HS), ( maplist(length,HS,_) -> maplist(sort,HS,S),
maplist(writeln,S),nl,writeln(Z) ), false ; true)).
attrs( R, [N-V|T]):- % extensible record, attribute specs
memberchk( N-X, R), % unique attribute names
X = V, % set, validate, or reject
( T = [] -> true ; attrs( R, T) ).
[color-yellow,drink-water, nation-norwegian, owns-cats, smoke-Dunhill ]
[color-blue, drink-tea, nation-dane, owns-horse, smoke-Blend ]
[color-red, drink-milk, nation-englishman,owns-birds, smoke-Pall Mall ]
[color-green, drink-coffee,nation-german, owns-zebra, smoke-Prince ]
[color-white, drink-beer, nation-swede, owns-dog, smoke-Blue Master]
specs( P, Hs, [Spec1]) :- call(P, A, Hs), attrs(A, Spec1).
specs( P, Hs, [Spec1, Spec2]) :- call(P, A, B, Hs), attrs(A, Spec1), attrs(B, Spec2).
german
% 234,852 inferences, 0.100 CPU in 0.170 seconds (59% CPU, 2345143 Lips)
true.
left_of( A, B, HS):- append( _, [A,B|_], HS).
next_to( A, B, HS):- left_of( A, B, HS) ; left_of( B, A, HS).
zebra( Zebra, Houses):- % a house *is* a collection of attributes
Houses = [A,_,C,_,_], % 1
maplist( specs( member, Houses),
[ [[ nation-englishman, color-red ]] % 2
, [[ nation-swede, owns -dog ]] % 3
, [[ nation-dane, drink-tea ]] % 4
, [[ drink -coffee, color-green ]] % 6
, [[ smoke -'Pall Mall', owns -birds ]] % 7
, [[ color -yellow, smoke-'Dunhill' ]] % 8
, [[ drink -beer, smoke-'Blue Master']] % 13
, [[ nation-german, smoke-'Prince' ]] % 14
] ),
specs( left_of, Houses, [[color -green ], [color-white ]]), % 5
maplist( attrs, [A, C], [[nation-norwegian], [drink-milk ]]), % 10, 9
maplist( specs( next_to, Houses),
[ [[smoke -'Blend' ], [owns -cats ]] % 11
, [[owns -horse ], [smoke-'Dunhill' ]] % 12
, [[nation-norwegian], [color-blue ]] % 15
, [[drink -water ], [smoke-'Blend' ]] % 16
] ),
specs( member, Houses, [[ owns-zebra, nation-Zebra]]).

View file

@ -1,45 +1,12 @@
:- initialization(main).
?- time(( zebra(Z,HS), ( maplist(length,HS,_) -> maplist(sort,HS,S),
maplist(writeln,S), nl, writeln(Z) ), false ; true)).
[color-yellow,drink-water, nation-norwegian, owns-cats, smoke-Dunhill ]
[color-blue, drink-tea, nation-dane, owns-horse, smoke-Blend ]
[color-red, drink-milk, nation-englishman,owns-birds, smoke-Pall Mall ]
[color-green, drink-coffee,nation-german, owns-zebra, smoke-Prince ]
[color-white, drink-beer, nation-swede, owns-dog, smoke-Blue Master]
zebra(X) :-
houses(Hs), member(h(_,X,zebra,_,_), Hs)
, findall(_, (member(H,Hs), write(H), nl), _), nl
, write('the one who keeps zebra: '), write(X), nl
.
houses(Hs) :-
Hs = [_,_,_,_,_] % 1
, H3 = h(_,_,_,milk,_), Hs = [_,_,H3,_,_] % 9
, H1 = h(_,nvg,_,_,_ ), Hs = [H1|_] % 10
, maplist( flip(member,Hs),
[ h(red,eng,_,_,_) % 2
, h(_,swe,dog,_,_) % 3
, h(_,dan,_,tea,_) % 4
, h(green,_,_,coffe,_) % 6
, h(_,_,birds,_,pm) % 7
, h(yellow,_,_,_,dh) % 8
, h(_,_,_,beer,bm) % 13
, h(_,ger,_,_,pri) % 14
])
, infix([ h(green,_,_,_,_)
, h(white,_,_,_,_) ], Hs) % 5
, maplist( flip(nextto,Hs),
[ [h(_,_,_,_,bl ), h(_,_,cats,_,_)] % 11
, [h(_,_,horse,_,_), h(_,_,_,_,dh )] % 12
, [h(_,nvg,_,_,_ ), h(blue,_,_,_,_)] % 15
, [h(_,_,_,water,_), h(_,_,_,_,bl )] % 16
])
.
flip(F,X,Y) :- call(F,Y,X).
infix(Xs,Ys) :- append(Xs,_,Zs) , append(_,Zs,Ys).
nextto(P,Xs) :- permutation(P,R), infix(R,Xs).
main :- findall(_, (zebra(_), nl), _), halt.
german
% 202,730 inferences, 0.031 CPU in 0.031 seconds (100% CPU, 6497715 Lips)
true.

View file

@ -0,0 +1,45 @@
:- initialization(main).
zebra(X) :-
houses(Hs), member(h(_,X,zebra,_,_), Hs)
, findall(_, (member(H,Hs), write(H), nl), _), nl
, write('the one who keeps zebra: '), write(X), nl
.
houses(Hs) :-
Hs = [_,_,_,_,_] % 1
, H3 = h(_,_,_,milk,_), Hs = [_,_,H3,_,_] % 9
, H1 = h(_,nvg,_,_,_ ), Hs = [H1|_] % 10
, maplist( flip(member,Hs),
[ h(red,eng,_,_,_) % 2
, h(_,swe,dog,_,_) % 3
, h(_,dan,_,tea,_) % 4
, h(green,_,_,coffe,_) % 6
, h(_,_,birds,_,pm) % 7
, h(yellow,_,_,_,dh) % 8
, h(_,_,_,beer,bm) % 13
, h(_,ger,_,_,pri) % 14
])
, infix([ h(green,_,_,_,_)
, h(white,_,_,_,_) ], Hs) % 5
, maplist( flip(nextto,Hs),
[ [h(_,_,_,_,bl ), h(_,_,cats,_,_)] % 11
, [h(_,_,horse,_,_), h(_,_,_,_,dh )] % 12
, [h(_,nvg,_,_,_ ), h(blue,_,_,_,_)] % 15
, [h(_,_,_,water,_), h(_,_,_,_,bl )] % 16
])
.
flip(F,X,Y) :- call(F,Y,X).
infix(Xs,Ys) :- append(Xs,_,Zs) , append(_,Zs,Ys).
nextto(P,Xs) :- permutation(P,R), infix(R,Xs).
main :- findall(_, (zebra(_), nl), _), halt.

View file

@ -1,101 +1,73 @@
import psyco; psyco.full()
from logpy import *
from logpy.core import lall
import time
class Content: elems= """Beer Coffee Milk Tea Water
Danish English German Norwegian Swedish
Blue Green Red White Yellow
Blend BlueMaster Dunhill PallMall Prince
Bird Cat Dog Horse Zebra""".split()
class Test: elems= "Drink Person Color Smoke Pet".split()
class House: elems= "One Two Three Four Five".split()
def lefto(q, p, list):
# give me q such that q is left of p in list
# zip(list, list[1:]) gives a list of 2-tuples of neighboring combinations
# which can then be pattern-matched against the query
return membero((q,p), zip(list, list[1:]))
for c in (Content, Test, House):
c.values = range(len(c.elems))
for i, e in enumerate(c.elems):
exec "%s.%s = %d" % (c.__name__, e, i)
def nexto(q, p, list):
# give me q such that q is next to p in list
# match lefto(q, p) OR lefto(p, q)
# requirement of vector args instead of tuples doesn't seem to be documented
return conde([lefto(q, p, list)], [lefto(p, q, list)])
def finalChecks(M):
def diff(a, b, ca, cb):
for h1 in House.values:
for h2 in House.values:
if M[ca][h1] == a and M[cb][h2] == b:
return h1 - h2
assert False
houses = var()
return abs(diff(Content.Norwegian, Content.Blue,
Test.Person, Test.Color)) == 1 and \
diff(Content.Green, Content.White,
Test.Color, Test.Color) == -1 and \
abs(diff(Content.Horse, Content.Dunhill,
Test.Pet, Test.Smoke)) == 1 and \
abs(diff(Content.Water, Content.Blend,
Test.Drink, Test.Smoke)) == 1 and \
abs(diff(Content.Blend, Content.Cat,
Test.Smoke, Test.Pet)) == 1
zebraRules = lall(
# there are 5 houses
(eq, (var(), var(), var(), var(), var()), houses),
# the Englishman's house is red
(membero, ('Englishman', var(), var(), var(), 'red'), houses),
# the Swede has a dog
(membero, ('Swede', var(), var(), 'dog', var()), houses),
# the Dane drinks tea
(membero, ('Dane', var(), 'tea', var(), var()), houses),
# the Green house is left of the White house
(lefto, (var(), var(), var(), var(), 'green'),
(var(), var(), var(), var(), 'white'), houses),
# coffee is the drink of the green house
(membero, (var(), var(), 'coffee', var(), 'green'), houses),
# the Pall Mall smoker has birds
(membero, (var(), 'Pall Mall', var(), 'birds', var()), houses),
# the yellow house smokes Dunhills
(membero, (var(), 'Dunhill', var(), var(), 'yellow'), houses),
# the middle house drinks milk
(eq, (var(), var(), (var(), var(), 'milk', var(), var()), var(), var()), houses),
# the Norwegian is the first house
(eq, (('Norwegian', var(), var(), var(), var()), var(), var(), var(), var()), houses),
# the Blend smoker is in the house next to the house with cats
(nexto, (var(), 'Blend', var(), var(), var()),
(var(), var(), var(), 'cats', var()), houses),
# the Dunhill smoker is next to the house where they have a horse
(nexto, (var(), 'Dunhill', var(), var(), var()),
(var(), var(), var(), 'horse', var()), houses),
# the Blue Master smoker drinks beer
(membero, (var(), 'Blue Master', 'beer', var(), var()), houses),
# the German smokes Prince
(membero, ('German', 'Prince', var(), var(), var()), houses),
# the Norwegian is next to the blue house
(nexto, ('Norwegian', var(), var(), var(), var()),
(var(), var(), var(), var(), 'blue'), houses),
# the house next to the Blend smoker drinks water
(nexto, (var(), 'Blend', var(), var(), var()),
(var(), var(), 'water', var(), var()), houses),
# one of the houses has a zebra--but whose?
(membero, (var(), var(), var(), 'zebra', var()), houses)
)
def constrained(M, atest):
if atest == Test.Drink:
return M[Test.Drink][House.Three] == Content.Milk
elif atest == Test.Person:
for h in House.values:
if ((M[Test.Person][h] == Content.Norwegian and
h != House.One) or
(M[Test.Person][h] == Content.Danish and
M[Test.Drink][h] != Content.Tea)):
return False
return True
elif atest == Test.Color:
for h in House.values:
if ((M[Test.Person][h] == Content.English and
M[Test.Color][h] != Content.Red) or
(M[Test.Drink][h] == Content.Coffee and
M[Test.Color][h] != Content.Green)):
return False
return True
elif atest == Test.Smoke:
for h in House.values:
if ((M[Test.Color][h] == Content.Yellow and
M[Test.Smoke][h] != Content.Dunhill) or
(M[Test.Smoke][h] == Content.BlueMaster and
M[Test.Drink][h] != Content.Beer) or
(M[Test.Person][h] == Content.German and
M[Test.Smoke][h] != Content.Prince)):
return False
return True
elif atest == Test.Pet:
for h in House.values:
if ((M[Test.Person][h] == Content.Swedish and
M[Test.Pet][h] != Content.Dog) or
(M[Test.Smoke][h] == Content.PallMall and
M[Test.Pet][h] != Content.Bird)):
return False
return finalChecks(M)
t0 = time.time()
solutions = run(0, houses, zebraRules)
t1 = time.time()
dur = t1-t0
def show(M):
for h in House.values:
print "%5s:" % House.elems[h],
for t in Test.values:
print "%10s" % Content.elems[M[t][h]],
print
count = len(solutions)
zebraOwner = [house for house in solutions[0] if 'zebra' in house][0][0]
def solve(M, t, n):
if n == 1 and constrained(M, t):
if t < 4:
solve(M, Test.values[t + 1], 5)
else:
show(M)
return
for i in xrange(n):
solve(M, t, n - 1)
M[t][0 if n % 2 else i], M[t][n - 1] = \
M[t][n - 1], M[t][0 if n % 2 else i]
def main():
M = [[None] * len(Test.elems) for _ in xrange(len(House.elems))]
for t in Test.values:
for h in House.values:
M[t][h] = Content.values[t * 5 + h]
solve(M, Test.Drink, 5)
main()
print "%i solutions in %.2f seconds" % (count, dur)
print "The %s is the owner of the zebra" % zebraOwner
print "Here are all the houses:"
for line in solutions[0]:
print str(line)

View file

@ -1,89 +1,101 @@
from itertools import permutations
import psyco
psyco.full()
import psyco; psyco.full()
class Number:elems= "One Two Three Four Five".split()
class Color: elems= "Red Green Blue White Yellow".split()
class Drink: elems= "Milk Coffee Water Beer Tea".split()
class Smoke: elems= "PallMall Dunhill Blend BlueMaster Prince".split()
class Pet: elems= "Dog Cat Zebra Horse Bird".split()
class Nation:elems= "British Swedish Danish Norvegian German".split()
class Content: elems= """Beer Coffee Milk Tea Water
Danish English German Norwegian Swedish
Blue Green Red White Yellow
Blend BlueMaster Dunhill PallMall Prince
Bird Cat Dog Horse Zebra""".split()
class Test: elems= "Drink Person Color Smoke Pet".split()
class House: elems= "One Two Three Four Five".split()
for c in (Number, Color, Drink, Smoke, Pet, Nation):
for c in (Content, Test, House):
c.values = range(len(c.elems))
for i, e in enumerate(c.elems):
exec "%s.%s = %d" % (c.__name__, e, i)
def is_possible(number, color, drink, smoke, pet):
if number and number[Nation.Norvegian] != Number.One:
return False
if color and color[Nation.British] != Color.Red:
return False
if drink and drink[Nation.Danish] != Drink.Tea:
return False
if smoke and smoke[Nation.German] != Smoke.Prince:
return False
if pet and pet[Nation.Swedish] != Pet.Dog:
return False
def finalChecks(M):
def diff(a, b, ca, cb):
for h1 in House.values:
for h2 in House.values:
if M[ca][h1] == a and M[cb][h2] == b:
return h1 - h2
assert False
if not number or not color or not drink or not smoke or not pet:
return True
return abs(diff(Content.Norwegian, Content.Blue,
Test.Person, Test.Color)) == 1 and \
diff(Content.Green, Content.White,
Test.Color, Test.Color) == -1 and \
abs(diff(Content.Horse, Content.Dunhill,
Test.Pet, Test.Smoke)) == 1 and \
abs(diff(Content.Water, Content.Blend,
Test.Drink, Test.Smoke)) == 1 and \
abs(diff(Content.Blend, Content.Cat,
Test.Smoke, Test.Pet)) == 1
for i in xrange(5):
if color[i] == Color.Green and drink[i] != Drink.Coffee:
return False
if smoke[i] == Smoke.PallMall and pet[i] != Pet.Bird:
return False
if color[i] == Color.Yellow and smoke[i] != Smoke.Dunhill:
return False
if number[i] == Number.Three and drink[i] != Drink.Milk:
return False
if smoke[i] == Smoke.BlueMaster and drink[i] != Drink.Beer:
return False
if color[i] == Color.Blue and number[i] != Number.Two:
return False
def constrained(M, atest):
if atest == Test.Drink:
return M[Test.Drink][House.Three] == Content.Milk
elif atest == Test.Person:
for h in House.values:
if ((M[Test.Person][h] == Content.Norwegian and
h != House.One) or
(M[Test.Person][h] == Content.Danish and
M[Test.Drink][h] != Content.Tea)):
return False
return True
elif atest == Test.Color:
for h in House.values:
if ((M[Test.Person][h] == Content.English and
M[Test.Color][h] != Content.Red) or
(M[Test.Drink][h] == Content.Coffee and
M[Test.Color][h] != Content.Green)):
return False
return True
elif atest == Test.Smoke:
for h in House.values:
if ((M[Test.Color][h] == Content.Yellow and
M[Test.Smoke][h] != Content.Dunhill) or
(M[Test.Smoke][h] == Content.BlueMaster and
M[Test.Drink][h] != Content.Beer) or
(M[Test.Person][h] == Content.German and
M[Test.Smoke][h] != Content.Prince)):
return False
return True
elif atest == Test.Pet:
for h in House.values:
if ((M[Test.Person][h] == Content.Swedish and
M[Test.Pet][h] != Content.Dog) or
(M[Test.Smoke][h] == Content.PallMall and
M[Test.Pet][h] != Content.Bird)):
return False
return finalChecks(M)
for j in xrange(5):
if (color[i] == Color.Green and
color[j] == Color.White and
number[j] - number[i] != 1):
return False
def show(M):
for h in House.values:
print "%5s:" % House.elems[h],
for t in Test.values:
print "%10s" % Content.elems[M[t][h]],
print
diff = abs(number[i] - number[j])
if smoke[i] == Smoke.Blend and pet[j] == Pet.Cat and diff != 1:
return False
if pet[i]==Pet.Horse and smoke[j]==Smoke.Dunhill and diff != 1:
return False
if smoke[i]==Smoke.Blend and drink[j]==Drink.Water and diff!=1:
return False
def solve(M, t, n):
if n == 1 and constrained(M, t):
if t < 4:
solve(M, Test.values[t + 1], 5)
else:
show(M)
return
return True
def show_row(t, data):
print "%6s: %12s%12s%12s%12s%12s" % (
t.__name__, t.elems[data[0]],
t.elems[data[1]], t.elems[data[2]],
t.elems[data[3]], t.elems[data[4]])
for i in xrange(n):
solve(M, t, n - 1)
M[t][0 if n % 2 else i], M[t][n - 1] = \
M[t][n - 1], M[t][0 if n % 2 else i]
def main():
perms = list(permutations(range(5)))
M = [[None] * len(Test.elems) for _ in xrange(len(House.elems))]
for t in Test.values:
for h in House.values:
M[t][h] = Content.values[t * 5 + h]
for number in perms:
if is_possible(number, None, None, None, None):
for color in perms:
if is_possible(number, color, None, None, None):
for drink in perms:
if is_possible(number, color, drink, None, None):
for smoke in perms:
if is_possible(number, color, drink, smoke, None):
for pet in perms:
if is_possible(number, color, drink, smoke, pet):
print "Found a solution:"
show_row(Nation, range(5))
show_row(Number, number)
show_row(Color, color)
show_row(Drink, drink)
show_row(Smoke, smoke)
show_row(Pet, pet)
print
solve(M, Test.Drink, 5)
main()

View file

@ -0,0 +1,51 @@
from itertools import permutations
class Number:elems= "One Two Three Four Five".split()
class Color: elems= "Red Green Blue White Yellow".split()
class Drink: elems= "Milk Coffee Water Beer Tea".split()
class Smoke: elems= "PallMall Dunhill Blend BlueMaster Prince".split()
class Pet: elems= "Dog Cat Zebra Horse Bird".split()
class Nation:elems= "British Swedish Danish Norvegian German".split()
for c in (Number, Color, Drink, Smoke, Pet, Nation):
for i, e in enumerate(c.elems):
exec "%s.%s = %d" % (c.__name__, e, i)
def show_row(t, data):
print "%6s: %12s%12s%12s%12s%12s" % (
t.__name__, t.elems[data[0]],
t.elems[data[1]], t.elems[data[2]],
t.elems[data[3]], t.elems[data[4]])
def main():
perms = list(permutations(range(5)))
for number in perms:
if number[Nation.Norvegian] == Number.One: # Constraint 10
for color in perms:
if color[Nation.British] == Color.Red: # Constraint 2
if number[color.index(Color.Blue)] == Number.Two: # Constraint 15+10
if number[color.index(Color.White)] - number[color.index(Color.Green)] == 1: # Constraint 5
for drink in perms:
if drink[Nation.Danish] == Drink.Tea: # Constraint 4
if drink[color.index(Color.Green)] == Drink.Coffee: # Constraint 6
if drink[number.index(Number.Three)] == Drink.Milk: # Constraint 9
for smoke in perms:
if smoke[Nation.German] == Smoke.Prince: # Constraint 14
if drink[smoke.index(Smoke.BlueMaster)] == Drink.Beer: # Constraint 13
if smoke[color.index(Color.Yellow)] == Smoke.Dunhill: # Constraint 8
if number[smoke.index(Smoke.Blend)] - number[drink.index(Drink.Water)] in (1, -1): # Constraint 16
for pet in perms:
if pet[Nation.Swedish] == Pet.Dog: # Constraint 3
if pet[smoke.index(Smoke.PallMall)] == Pet.Bird: # Constraint 7
if number[pet.index(Pet.Horse)] - number[smoke.index(Smoke.Dunhill)] in (1, -1): # Constraint 12
if number[smoke.index(Smoke.Blend)] - number[pet.index(Pet.Cat)] in (1, -1): # Constraint 11
print "Found a solution:"
show_row(Nation, range(5))
show_row(Number, number)
show_row(Color, color)
show_row(Drink, drink)
show_row(Smoke, smoke)
show_row(Pet, pet)
print
main()

View file

@ -0,0 +1,170 @@
/* REXX ---------------------------------------------------------------
* Solve the Zebra Puzzle
*--------------------------------------------------------------------*/
oid='zebra.txt'; 'erase' oid
Call mk_perm /* compute all permutations */
Call encode /* encode the elements of the specifucations */
/* ex2 .. eg16 the formalized specifications */
solutions=0
Call time 'R'
Do nation_i = 1 TO 120
Nations = perm.nation_i
IF ex10() Then Do
Do color_i = 1 TO 120
Colors = perm.color_i
IF ex5() & ex2() & ex15() Then Do
Do drink_i = 1 TO 120
Drinks = perm.drink_i
IF ex9() & ex4() & ex6() Then Do
Do smoke_i = 1 TO 120
Smokes = perm.smoke_i
IF ex14() & ex13() & ex16() & ex8() Then Do
Do animal_i = 1 TO 120
Animals = perm.animal_i
IF ex3() & ex7() & ex11() & ex12() Then Do
/* Call out 'Drinks =' Drinks 54321 Wat Tea Mil Cof Bee */
/* Call out 'Nations=' Nations 41235 Nor Den Eng Ger Swe */
/* Call out 'Colors =' Colors 51324 Yel Blu Red Gre Whi */
/* Call out 'Smokes =' Smokes 31452 Dun Ble Pal Pri Blu */
/* Call out 'Animals=' Animals 24153 Cat Hor Bir Zeb Dog */
Call out 'House Drink Nation Colour'||,
' Smoke Animal'
Do i=1 To 5
di=substr(drinks,i,1)
ni=substr(nations,i,1)
ci=substr(colors,i,1)
si=substr(smokes,i,1)
ai=substr(animals,i,1)
ol.i=right(i,3)' '||left(drink.di,11),
||left(nation.ni,11),
||left(color.ci,11),
||left(smoke.si,11),
||left(animal.ai,11)
Call out ol.i
End
solutions+=1
End
End /* animal_i */
End
End /* smoke_i */
End
End /* drink_i */
End
End /* colr_i */
End
End /* nation_i */
Say 'Number of solutions =' solutions
Say 'Solved in' time('E') 'seconds'
Exit
/*------------------------------------------------------------------------------
#There are five houses.
ex2: #The English man lives in the red house.
ex3: #The Swede has a dog.
ex4: #The Dane drinks tea.
ex5: #The green house is immediately to the left of the white house.
ex6: #They drink coffee in the green house.
ex7: #The man who smokes Pall Mall has birds.
ex8: #In the yellow house they smoke Dunhill.
ex9: #In the middle house they drink milk.
ex10: #The Norwegian lives in the first house.
ex11: #The man who smokes Blend lives in the house next to the house with cats.
ex12: #In a house next to the house where they have a horse, they smoke Dunhill.
ex13: #The man who smokes Blue Master drinks beer.
ex14: #The German smokes Prince.
ex15: #The Norwegian lives next to the blue house.
ex16: #They drink water in a house next to the house where they smoke Blend.
------------------------------------------------------------------------------*/
ex2: Return pos(England,Nations)=pos(Red,Colors)
ex3: Return pos(Sweden,Nations)=pos(Dog,Animals)
ex4: Return pos(Denmark,Nations)=pos(Tea,Drinks)
ex5: Return pos(Green,Colors)=pos(White,Colors)-1
ex6: Return pos(Coffee,Drinks)=pos(Green,Colors)
ex7: Return pos(PallMall,Smokes)=pos(Birds,Animals)
ex8: Return pos(Dunhill,Smokes)=pos(Yellow,Colors)
ex9: Return substr(Drinks,3,1)=Milk
ex10: Return left(Nations,1)=Norway
ex11: Return abs(pos(Blend,Smokes)-pos(Cats,Animals))=1
ex12: Return abs(pos(Dunhill,Smokes)-pos(Horse,Animals))=1
ex13: Return pos(BlueMaster,Smokes)=pos(Beer,Drinks)
ex14: Return pos(Germany,Nations)=pos(Prince,Smokes)
ex15: Return abs(pos(Norway,Nations)-pos(Blue,Colors))=1
ex16: Return abs(pos(Blend,Smokes)-pos(Water,Drinks))=1
mk_perm: Procedure Expose perm.
/*---------------------------------------------------------------------
* Make all permutations of 12345 in perm.*
*--------------------------------------------------------------------*/
perm.=0
n=5
Do pop=1 For n
p.pop=pop
End
Call store
Do While nextperm(n,0)
Call store
End
Return
nextperm: Procedure Expose p. perm.
Parse Arg n,i
nm=n-1
Do k=nm By-1 For nm
kp=k+1
If p.k<p.kp Then Do
i=k
Leave
End
End
Do j=i+1 While j<n
Parse Value p.j p.n With p.n p.j
n=n-1
End
If i>0 Then Do
Do j=i+1 While p.j<p.i
End
Parse Value p.j p.i With p.i p.j
End
Return i>0
store: Procedure Expose p. perm.
z=perm.0+1
_=''
Do j=1 To 5
_=_||p.j
End
perm.z=_
perm.0=z
Return
encode:
Beer=1 ; Drink.1='Beer'
Coffee=2 ; Drink.2='Coffee'
Milk=3 ; Drink.3='Milk'
Tea=4 ; Drink.4='Tea'
Water=5 ; Drink.5='Water'
Denmark=1 ; Nation.1='Denmark'
England=2 ; Nation.2='England'
Germany=3 ; Nation.3='Germany'
Norway=4 ; Nation.4='Norway'
Sweden=5 ; Nation.5='Sweden'
Blue=1 ; Color.1='Blue'
Green=2 ; Color.2='Green'
Red=3 ; Color.3='Red'
White=4 ; Color.4='White'
Yellow=5 ; Color.5='Yellow'
Blend=1 ; Smoke.1='Blend'
BlueMaster=2 ; Smoke.2='BlueMaster'
Dunhill=3 ; Smoke.3='Dunhill'
PallMall=4 ; Smoke.4='PallMall'
Prince=5 ; Smoke.5='Prince'
Birds=1 ; Animal.1='Birds'
Cats=2 ; Animal.2='Cats'
Dog=3 ; Animal.3='Dog'
Horse=4 ; Animal.4='Horse'
Zebra=5 ; Animal.5='Zebra'
Return
out:
Say arg(1)
Return lineout(oid,arg(1))

View file

@ -1,13 +1,9 @@
# Solve Zebra Puzzle.
#
# Nigel_Galloway
# August 31st., 2014.
CONTENT = { :House => nil,
:Nationality => [:English, :Swedish, :Danish, :Norwegian, :German],
:Colour => [:Red, :Green, :White, :Blue, :Yellow],
:Pet => [:Dog, :Birds, :Cats, :Horse, :Zebra],
:Drink => [:Tea, :Coffee, :Milk, :Beer, :Water],
:Smoke => [:PallMall, :Dunhill, :BlueMaster, :Prince, :Blend] }
CONTENT = { House: '',
Nationality: %i[English Swedish Danish Norwegian German],
Colour: %i[Red Green White Blue Yellow],
Pet: %i[Dog Birds Cats Horse Zebra],
Drink: %i[Tea Coffee Milk Beer Water],
Smoke: %i[PallMall Dunhill BlueMaster Prince Blend] }
def adjacent? (n,i,g,e)
(0..3).any?{|x| (n[x]==i and g[x+1]==e) or (n[x+1]==i and g[x]==e)}
@ -21,33 +17,38 @@ def coincident? (n,i,g,e)
n.each_index.any?{|x| n[x]==i and g[x]==e}
end
def solve
def solve_zebra_puzzle
CONTENT[:Nationality].permutation{|nation|
next if nation.first != :Norwegian # 10
next unless nation.first == :Norwegian # 10
CONTENT[:Colour].permutation{|colour|
next unless leftof?(colour,:Green,colour,:White) # 5
next unless coincident?(nation,:English,colour,:Red) # 2
next unless adjacent?(nation,:Norwegian,colour,:Blue) # 15
next unless leftof?(colour, :Green, colour, :White) # 5
next unless coincident?(nation, :English, colour, :Red) # 2
next unless adjacent?(nation, :Norwegian, colour, :Blue) # 15
CONTENT[:Pet].permutation{|pet|
next unless coincident?(nation,:Swedish,pet,:Dog) # 3
next unless coincident?(nation, :Swedish, pet, :Dog) # 3
CONTENT[:Drink].permutation{|drink|
next if drink[2] != :Milk # 9
next unless coincident?(nation,:Danish,drink,:Tea) # 4
next unless coincident?(colour,:Green,drink,:Coffee) # 6
next unless drink[2] == :Milk # 9
next unless coincident?(nation, :Danish, drink, :Tea) # 4
next unless coincident?(colour, :Green, drink, :Coffee) # 6
CONTENT[:Smoke].permutation{|smoke|
next unless coincident?(smoke,:PallMall,pet,:Birds) # 7
next unless coincident?(smoke,:Dunhill,colour,:Yellow) # 8
next unless coincident?(smoke,:BlueMaster,drink,:Beer) # 13
next unless coincident?(smoke,:Prince,nation,:German) # 14
next unless adjacent?(smoke,:Blend,pet,:Cats) # 11
next unless adjacent?(smoke,:Blend,drink,:Water) # 16
next unless adjacent?(smoke,:Dunhill,pet,:Horse) # 12
return [nation,colour,pet,drink,smoke]
next unless coincident?(smoke, :PallMall, pet, :Birds) # 7
next unless coincident?(smoke, :Dunhill, colour, :Yellow) # 8
next unless coincident?(smoke, :BlueMaster, drink, :Beer) # 13
next unless coincident?(smoke, :Prince, nation, :German) # 14
next unless adjacent?(smoke, :Blend, pet, :Cats) # 11
next unless adjacent?(smoke, :Blend, drink, :Water) # 16
next unless adjacent?(smoke, :Dunhill,pet, :Horse) # 12
print_out(nation, colour, pet, drink, smoke)
} } } } }
end
res = solve
width = CONTENT.map{|x| x.flatten.map{|y|y.to_s.size}.max}
fmt = width.map{|w| "%-#{w}s"}.join(" ")
puts "The Zebra is owned by the man who is #{res[0][res[2].find_index(:Zebra)]}",""
puts fmt % CONTENT.keys, fmt % width.map{|w| "-"*w}
res.transpose.each.with_index(1){|x,n| puts fmt % [n,*x]}
def print_out (nation, colour, pet, drink, smoke)
width = CONTENT.map{|x| x.flatten.map{|y|y.size}.max}
fmt = width.map{|w| "%-#{w}s"}.join(" ")
national = nation[ pet.find_index(:Zebra) ]
puts "The Zebra is owned by the man who is #{national}",""
puts fmt % CONTENT.keys, fmt % width.map{|w| "-"*w}
[nation,colour,pet,drink,smoke].transpose.each.with_index(1){|x,n| puts fmt % [n,*x]}
end
solve_zebra_puzzle

View file

@ -0,0 +1,85 @@
import scala.util.Try
object Einstein extends App {
// The strategy here is to mount a brute-force attack on the solution space, pruning very aggressively.
// The scala standard `permutations` method is extremely helpful here. It turns out that by pruning
// quickly and smartly we can solve this very quickly (45ms on my machine) compared to days or weeks
// required to fully enumerate the solution space.
// We set up a for comprehension with an enumerator for each of the 5 variables, with if clauses to
// prune. The hard part is the pruning logic, which is basically just translating the rules to code
// and the data model. The data model is basically Seq[Seq[String]]
// Rules are encoded as for comprehension filters. There is a natural cascade of rules from
// depending on more or less criteria. The rules about smokes are the most complex and depend
// on the most other factors
// 4. The green house is just to the left of the white one.
def colorRules(colors: Seq[String]) = Try(colors(colors.indexOf("White") - 1) == "Green").getOrElse(false)
// 1. The Englishman lives in the red house.
// 9. The Norwegian lives in the first house.
// 14. The Norwegian lives next to the blue house.
def natRules(colors: Seq[String], nats: Seq[String]) =
nats.head == "Norwegian" && colors(nats.indexOf("Brit")) == "Red" &&
(Try(colors(nats.indexOf("Norwegian") - 1) == "Blue").getOrElse(false) ||
Try(colors(nats.indexOf("Norwegian") + 1) == "Blue").getOrElse(false))
// 3. The Dane drinks tea.
// 5. The owner of the green house drinks coffee.
// 8. The man in the center house drinks milk.
def drinkRules(colors: Seq[String], nats: Seq[String], drinks: Seq[String]) =
drinks(nats.indexOf("Dane")) == "Tea" &&
drinks(colors.indexOf("Green")) == "Coffee" &&
drinks(2) == "Milk"
// 2. The Swede keeps dogs.
def petRules(nats: Seq[String], pets: Seq[String]) = pets(nats.indexOf("Swede")) == "Dogs"
// 6. The Pall Mall smoker keeps birds.
// 7. The owner of the yellow house smokes Dunhills.
// 10. The Blend smoker has a neighbor who keeps cats.
// 11. The man who smokes Blue Masters drinks bier.
// 12. The man who keeps horses lives next to the Dunhill smoker.
// 13. The German smokes Prince.
// 15. The Blend smoker has a neighbor who drinks water.
def smokeRules(colors: Seq[String], nats: Seq[String], drinks: Seq[String], pets: Seq[String], smokes: Seq[String]) =
pets(smokes.indexOf("Pall Mall")) == "Birds" &&
smokes(colors.indexOf("Yellow")) == "Dunhill" &&
(Try(pets(smokes.indexOf("Blend") - 1) == "Cats").getOrElse(false) ||
Try(pets(smokes.indexOf("Blend") + 1) == "Cats").getOrElse(false)) &&
drinks(smokes.indexOf("BlueMaster")) == "Beer" &&
(Try(smokes(pets.indexOf("Horses") - 1) == "Dunhill").getOrElse(false) ||
Try(pets(pets.indexOf("Horses") + 1) == "Dunhill").getOrElse(false)) &&
smokes(nats.indexOf("German")) == "Prince" &&
(Try(drinks(smokes.indexOf("Blend") - 1) == "Water").getOrElse(false) ||
Try(drinks(smokes.indexOf("Blend") + 1) == "Water").getOrElse(false))
// once the rules are created it, the actual solution is simple: iterate brute force, pruning early.
val solutions = for {
colors <- Seq("Red", "Blue", "White", "Green", "Yellow").permutations if colorRules(colors)
nats <- Seq("Brit", "Swede", "Dane", "Norwegian", "German").permutations if natRules(colors, nats)
drinks <- Seq("Tea", "Coffee", "Milk", "Beer", "Water").permutations if drinkRules(colors, nats, drinks)
pets <- Seq("Dogs", "Birds", "Cats", "Horses", "Fish").permutations if petRules(nats, pets)
smokes <- Seq("BlueMaster", "Blend", "Pall Mall", "Dunhill", "Prince").permutations if smokeRules(colors, nats, drinks, pets, smokes)
} yield Seq(colors, nats, drinks, pets, smokes)
// There *should* be just one solution...
solutions.foreach { solution =>
// so we can pretty-print, find out the maximum strength length of all cells
val maxLen = solution.flatten.map(_.length).max
def pretty(str: String): String = str + (" " * (maxLen - str.length + 1))
// a labels column
val labels = ("" +: Seq("Color", "Nation", "Drink", "Pet", "Smoke").map(_ + ":")).toIterator
// print each row including a column header
((1 to 5).map(n => s"House $n") +: solution).map(_.map(pretty)).map(x => (pretty(labels.next) +: x).mkString(" ")).foreach(println)
println()
println(s"The ${solution(1)(solution(3).indexOf("Fish"))} owns the Fish")
}
}

View file

@ -0,0 +1,249 @@
(* Attributes and values *)
val str_attributes = Vector.fromList ["Color", "Nation", "Drink", "Pet", "Smoke"]
val str_colors = Vector.fromList ["Red", "Green", "White", "Yellow", "Blue"]
val str_nations = Vector.fromList ["English", "Swede", "Dane", "German", "Norwegian"]
val str_drinks = Vector.fromList ["Tea", "Coffee", "Milk", "Beer", "Water"]
val str_pets = Vector.fromList ["Dog", "Birds", "Cats", "Horse", "Zebra"]
val str_smokes = Vector.fromList ["PallMall", "Dunhill", "Blend", "BlueMaster", "Prince"]
val (Color, Nation, Drink, Pet, Smoke) = (0, 1, 2, 3, 4) (* Attributes *)
val (Red, Green, White, Yellow, Blue) = (0, 1, 2, 3, 4) (* Color *)
val (English, Swede, Dane, German, Norwegian) = (0, 1, 2, 3, 4) (* Nation *)
val (Tea, Coffee, Milk, Beer, Water) = (0, 1, 2, 3, 4) (* Drink *)
val (Dog, Birds, Cats, Horse, Zebra) = (0, 1, 2, 3, 4) (* Pet *)
val (PallMall, Dunhill, Blend, BlueMaster, Prince) = (0, 1, 2, 3, 4) (* Smoke *)
type attr = int
type value = int
type houseno = int
(* Rules *)
datatype rule =
AttrPairRule of (attr * value) * (attr * value)
| NextToRule of (attr * value) * (attr * value)
| LeftOfRule of (attr * value) * (attr * value)
(* Conditions *)
val rules = [
AttrPairRule ((Nation, English), (Color, Red)), (* #02 *)
AttrPairRule ((Nation, Swede), (Pet, Dog)), (* #03 *)
AttrPairRule ((Nation, Dane), (Drink, Tea)), (* #04 *)
LeftOfRule ((Color, Green), (Color, White)), (* #05 *)
AttrPairRule ((Color, Green), (Drink, Coffee)), (* #06 *)
AttrPairRule ((Smoke, PallMall), (Pet, Birds)), (* #07 *)
AttrPairRule ((Smoke, Dunhill), (Color, Yellow)), (* #08 *)
NextToRule ((Smoke, Blend), (Pet, Cats)), (* #11 *)
NextToRule ((Smoke, Dunhill), (Pet, Horse)), (* #12 *)
AttrPairRule ((Smoke, BlueMaster), (Drink, Beer)), (* #13 *)
AttrPairRule ((Nation, German), (Smoke, Prince)), (* #14 *)
NextToRule ((Nation, Norwegian), (Color, Blue)), (* #15 *)
NextToRule ((Smoke, Blend), (Drink, Water))] (* #16 *)
type house = value option * value option * value option * value option * value option
fun houseval ((a, b, c, d, e) : house, 0 : attr) = a
| houseval ((a, b, c, d, e) : house, 1 : attr) = b
| houseval ((a, b, c, d, e) : house, 2 : attr) = c
| houseval ((a, b, c, d, e) : house, 3 : attr) = d
| houseval ((a, b, c, d, e) : house, 4 : attr) = e
| houseval _ = raise Domain
fun sethouseval ((a, b, c, d, e) : house, 0 : attr, a2 : value option) = (a2, b, c, d, e )
| sethouseval ((a, b, c, d, e) : house, 1 : attr, b2 : value option) = (a, b2, c, d, e )
| sethouseval ((a, b, c, d, e) : house, 2 : attr, c2 : value option) = (a, b, c2, d, e )
| sethouseval ((a, b, c, d, e) : house, 3 : attr, d2 : value option) = (a, b, c, d2, e )
| sethouseval ((a, b, c, d, e) : house, 4 : attr, e2 : value option) = (a, b, c, d, e2)
| sethouseval _ = raise Domain
fun getHouseVal houses (no, attr) = houseval (Array.sub (houses, no), attr)
fun setHouseVal houses (no, attr, newval) =
Array.update (houses, no, sethouseval (Array.sub (houses, no), attr, newval))
fun match (house, (rule_attr, rule_val)) =
let
val value = houseval (house, rule_attr)
in
isSome value andalso valOf value = rule_val
end
fun matchNo houses (no, rule) =
match (Array.sub (houses, no), rule)
fun compare (house1, house2, ((rule_attr1, rule_val1), (rule_attr2, rule_val2))) =
let
val val1 = houseval (house1, rule_attr1)
val val2 = houseval (house2, rule_attr2)
in
if isSome val1 andalso isSome val2
then (valOf val1 = rule_val1 andalso valOf val2 <> rule_val2)
orelse
(valOf val1 <> rule_val1 andalso valOf val2 = rule_val2)
else false
end
fun compareNo houses (no1, no2, rulepair) =
compare (Array.sub (houses, no1), Array.sub (houses, no2), rulepair)
fun invalid houses no (AttrPairRule rulepair) =
compareNo houses (no, no, rulepair)
| invalid houses no (NextToRule rulepair) =
(if no > 0
then compareNo houses (no, no-1, rulepair)
else true)
andalso
(if no < 4
then compareNo houses (no, no+1, rulepair)
else true)
| invalid houses no (LeftOfRule rulepair) =
if no > 0
then compareNo houses (no-1, no, rulepair)
else matchNo houses (no, #1rulepair)
(*
* val checkRulesForNo : house vector -> houseno -> bool
* Check all rules for a house;
* Returns true, when one rule was invalid.
*)
fun checkRulesForNo (houses : house array) no =
let
exception RuleError
in
(map (fn rule => if invalid houses no rule then raise RuleError else ()) rules;
false)
handle RuleError => true
end
(*
* val checkAll : house vector -> bool
* Check all rules;
* return true if everything is ok.
*)
fun checkAll (houses : house array) =
let
exception RuleError
in
(map (fn no => if checkRulesForNo houses no then raise RuleError else ()) [0,1,2,3,4];
true)
handle RuleError => false
end
(*
*
* House printing for debugging
*
*)
fun valToString (0, SOME a) = Vector.sub (str_colors, a)
| valToString (1, SOME b) = Vector.sub (str_nations, b)
| valToString (2, SOME c) = Vector.sub (str_drinks, c)
| valToString (3, SOME d) = Vector.sub (str_pets, d)
| valToString (4, SOME e) = Vector.sub (str_smokes, e)
| valToString _ = "-"
(*
* Note:
* Format needs SML NJ
*)
fun printHouse no ((a, b, c, d, e) : house) =
(
print (Format.format "%12d" [Format.LEFT (12, Format.INT no)]);
print (Format.format "%12s%12s%12s%12s%12s"
(map (fn (x, y) => Format.LEFT (12, Format.STR (valToString (x, y))))
[(0,a), (1,b), (2,c), (3,d), (4,e)]));
print ("\n")
)
fun printHouses houses =
(
print (Format.format "%12s" [Format.LEFT (12, Format.STR "House")]);
Vector.map (fn a => print (Format.format "%12s" [Format.LEFT (12, Format.STR a)]))
str_attributes;
print "\n";
Array.foldli (fn (no, house, _) => printHouse no house) () houses
)
(*
*
* Solving
*
*)
exception SolutionFound
fun search (houses : house array, used : bool Array2.array) (no : houseno, attr : attr) =
let
val i = ref 0
val (nextno, nextattr) = if attr < 4 then (no, attr + 1) else (no + 1, 0)
in
if isSome (getHouseVal houses (no, attr))
then
(
search (houses, used) (nextno, nextattr)
)
else
(
while (!i < 5)
do
(
if Array2.sub (used, attr, !i) then ()
else
(
Array2.update (used, attr, !i, true);
setHouseVal houses (no, attr, SOME (!i));
if checkAll houses then
(
if no = 4 andalso attr = 4
then raise SolutionFound
else search (houses, used) (nextno, nextattr)
)
else ();
Array2.update (used, attr, !i, false)
); (* else *)
i := !i + 1
); (* do *)
setHouseVal houses (no, attr, NONE)
) (* else *)
end
fun init () =
let
val unknown : house = (NONE, NONE, NONE, NONE, NONE)
val houses = Array.fromList [unknown, unknown, unknown, unknown, unknown]
val used = Array2.array (5, 5, false)
in
(houses, used)
end
fun solve () =
let
val (houses, used) = init()
in
setHouseVal houses (2, Drink, SOME Milk); (* #09 *)
Array2.update (used, Drink, Milk, true);
setHouseVal houses (0, Nation, SOME Norwegian); (* #10 *)
Array2.update (used, Nation, Norwegian, true);
(search (houses, used) (0, 0); NONE)
handle SolutionFound => SOME houses
end
(*
*
* Execution
*
*)
fun main () = let
val solution = solve()
in
if isSome solution
then printHouses (valOf solution)
else print "No solution found!\n"
end