Edit: this puzzle is also known as "Einstein's Riddle"
The Who owns the Zebra (you can try the online version here) is an example of a classic set of puzzles and I bet that most people on Stack Overflow can solve it with pen and paper. But what would a programmatic solution look like?
Based on the clues listed below...
There are five houses.
Each house has its own unique color.
All house owners are of different nationalities.
They all have different pets.
They all drink different drinks.
They all smoke different cigarettes.
The English man lives in the red house.
The Swede has a dog.
The Dane drinks tea.
The green house is on the left side of the white house.
They drink coffee in the green house.
The man who smokes Pall Mall has birds.
In the yellow house they smoke Dunhill.
In the middle house they drink milk.
The Norwegian lives in the first house.
The man who smokes Blend lives in the house next to the house with cats.
In the house next to the house where they have a horse, they smoke Dunhill.
The man who smokes Blue Master drinks beer.
The German smokes Prince.
The Norwegian lives next to the blue house.
They drink water in the house next to the house where they smoke Blend.
...who owns the Zebra?
Here's a solution in Python based on constraint-programming:
from constraint import AllDifferentConstraint, InSetConstraint, Problem
# variables
colors = "blue red green white yellow".split()
nationalities = "Norwegian German Dane Swede English".split()
pets = "birds dog cats horse zebra".split()
drinks = "tea coffee milk beer water".split()
cigarettes = "Blend, Prince, Blue Master, Dunhill, Pall Mall".split(", ")
# There are five houses.
minn, maxn = 1, 5
problem = Problem()
# value of a variable is the number of a house with corresponding property
variables = colors + nationalities + pets + drinks + cigarettes
problem.addVariables(variables, range(minn, maxn+1))
# Each house has its own unique color.
# All house owners are of different nationalities.
# They all have different pets.
# They all drink different drinks.
# They all smoke different cigarettes.
for vars_ in (colors, nationalities, pets, drinks, cigarettes):
problem.addConstraint(AllDifferentConstraint(), vars_)
# In the middle house they drink milk.
#NOTE: interpret "middle" in a numerical sense (not geometrical)
problem.addConstraint(InSetConstraint([(minn + maxn) // 2]), ["milk"])
# The Norwegian lives in the first house.
#NOTE: interpret "the first" as a house number
problem.addConstraint(InSetConstraint([minn]), ["Norwegian"])
# The green house is on the left side of the white house.
#XXX: what is "the left side"? (linear, circular, two sides, 2D house arrangment)
#NOTE: interpret it as 'green house number' + 1 == 'white house number'
problem.addConstraint(lambda a,b: a+1 == b, ["green", "white"])
def add_constraints(constraint, statements, variables=variables, problem=problem):
for stmt in (line for line in statements if line.strip()):
problem.addConstraint(constraint, [v for v in variables if v in stmt])
and_statements = """
They drink coffee in the green house.
The man who smokes Pall Mall has birds.
The English man lives in the red house.
The Dane drinks tea.
In the yellow house they smoke Dunhill.
The man who smokes Blue Master drinks beer.
The German smokes Prince.
The Swede has a dog.
""".split("\n")
add_constraints(lambda a,b: a == b, and_statements)
nextto_statements = """
The man who smokes Blend lives in the house next to the house with cats.
In the house next to the house where they have a horse, they smoke Dunhill.
The Norwegian lives next to the blue house.
They drink water in the house next to the house where they smoke Blend.
""".split("\n")
#XXX: what is "next to"? (linear, circular, two sides, 2D house arrangment)
add_constraints(lambda a,b: abs(a - b) == 1, nextto_statements)
def solve(variables=variables, problem=problem):
from itertools import groupby
from operator import itemgetter
# find & print solutions
for solution in problem.getSolutionIter():
for key, group in groupby(sorted(solution.iteritems(), key=itemgetter(1)), key=itemgetter(1)):
print key,
for v in sorted(dict(group).keys(), key=variables.index):
print v.ljust(9),
print
if __name__ == '__main__':
solve()
Output:
1 yellow Norwegian cats water Dunhill
2 blue Dane horse tea Blend
3 red English birds milk Pall Mall
4 green German zebra coffee Prince
5 white Swede dog beer Blue Master
It takes 0.6 seconds (CPU 1.5GHz) to find the solution.
The answer is "German owns zebra."
To install the constraint module via pip:
pip install python-constraint
To install manually:
download:
$ wget https://pypi.python.org/packages/source/p/python-constraint/python-constraint-1.2.tar.bz2#md5=d58de49c85992493db53fcb59b9a0a45
extract (Linux/Mac/BSD):
$ bzip2 -cd python-constraint-1.2.tar.bz2 | tar xvf -
extract (Windows, with 7zip):
> 7z e python-constraint-1.2.tar.bz2
> 7z e python-constraint-1.2.tar
install:
$ cd python-constraint-1.2
$ python setup.py install
In Prolog, we can instantiate the domain just by selecting elements from it :) (making mutually-exclusive choices, for efficiency). Using SWI-Prolog,
select([A|As],S):- select(A,S,S1),select(As,S1).
select([],_).
left_of(A,B,C):- append(_,[A,B|_],C).
next_to(A,B,C):- left_of(A,B,C) ; left_of(B,A,C).
zebra(Owns, HS):- % (* house: color,nation,pet,drink,smokes *)
HS = [ h(_,norwegian,_,_,_), h(blue,_,_,_,_), h(_,_,_,milk,_), _, _],
select([ h(red,brit,_,_,_), h(_,swede,dog,_,_),
h(_,dane,_,tea,_), h(_,german,_,_,prince)], HS),
select([ h(_,_,birds,_,pallmall), h(yellow,_,_,_,dunhill),
h(_,_,_,beer,bluemaster)], HS),
left_of( h(green,_,_,coffee,_), h(white,_,_,_,_), HS),
next_to( h(_,_,_,_,dunhill), h(_,_,horse,_,_), HS),
next_to( h(_,_,_,_,blend), h(_,_,cats, _,_), HS),
next_to( h(_,_,_,_,blend), h(_,_,_,water,_), HS),
member( h(_,Owns,zebra,_,_), HS).
Runs quite instantly:
?- time( (zebra(Who,HS), writeln(Who), nl, maplist(writeln,HS), nl, false
; writeln("no more solutions!") )).
german
h( yellow, norwegian, cats, water, dunhill )
h( blue, dane, horse, tea, blend )
h( red, brit, birds, milk, pallmall )
h( green, german, zebra, coffee, prince ) % (* formatted by hand *)
h( white, swede, dog, beer, bluemaster)
no more solutions!
% (* 1,706 inferences, 0.000 CPU in 0.070 seconds (0% CPU, Infinite Lips) *)
true.
One poster already mentioned that Prolog is a potential solution. This is true, and it's the solution I would use. In more general terms, this is a perfect problem for an automated inference system. Prolog is a logic programming language (and associated interpreter) that form such a system. It basically allows concluding of facts from statements made using First Order Logic. FOL is basically a more advanced form of propositional logic. If you decide you don't want to use Prolog, you could use a similar system of your own creation using a technique such as modus ponens to perform the draw the conclusions.
You will, of course, need to add some rules about zebras, since it isn't mentioned anywhere... I believe the intent is that you can figure out the other 4 pets and thus deduce the last one is the zebra? You'll want to add rules that state a zebra is one of the pets, and each house can only have one pet. Getting this kind of "common sense" knowledge into an inference system is the major hurdle to using the technique as a true AI. There are some research projects, such as Cyc, which are attempting to give such common knowledge through brute force. They've met with an interesting amount of success.
SWI-Prolog compatible:
% NOTE - This may or may not be more efficent. A bit verbose, though.
left_side(L, R, [L, R, _, _, _]).
left_side(L, R, [_, L, R, _, _]).
left_side(L, R, [_, _, L, R, _]).
left_side(L, R, [_, _, _, L, R]).
next_to(X, Y, Street) :- left_side(X, Y, Street).
next_to(X, Y, Street) :- left_side(Y, X, Street).
m(X, Y) :- member(X, Y).
get_zebra(Street, Who) :-
Street = [[C1, N1, P1, D1, S1],
[C2, N2, P2, D2, S2],
[C3, N3, P3, D3, S3],
[C4, N4, P4, D4, S4],
[C5, N5, P5, D5, S5]],
m([red, english, _, _, _], Street),
m([_, swede, dog, _, _], Street),
m([_, dane, _, tea, _], Street),
left_side([green, _, _, _, _], [white, _, _, _, _], Street),
m([green, _, _, coffee, _], Street),
m([_, _, birds, _, pallmall], Street),
m([yellow, _, _, _, dunhill], Street),
D3 = milk,
N1 = norwegian,
next_to([_, _, _, _, blend], [_, _, cats, _, _], Street),
next_to([_, _, horse, _, _], [_, _, _, _, dunhill], Street),
m([_, _, _, beer, bluemaster], Street),
m([_, german, _, _, prince], Street),
next_to([_, norwegian, _, _, _], [blue, _, _, _, _], Street),
next_to([_, _, _, water, _], [_, _, _, _, blend], Street),
m([_, Who, zebra, _, _], Street).
At the interpreter:
?- get_zebra(Street, Who).
Street = ...
Who = german
Here's how I'd go about it. First I'd generate all the ordered n-tuples
(housenumber, color, nationality, pet, drink, smoke)
5^6 of those, 15625, easily manageable. Then I'd filter out the simple boolean conditions. there's ten of them, and each of those you'd expect to filter out 8/25 of the conditions (1/25 of the conditions contain a Swede with a dog, 16/25 contain a non-Swede with a non-dog). Of course they're not independent but after filtering those out there shouldn't be many left.
After that, you've got a nice graph problem. Create a graph with each node representing one of the remaining n-tuples. Add edges to the graph if the two ends contain duplicates in some n-tuple position or violate any 'positional' constraints (there's five of those). From there you're almost home, search the graph for an independent set of five nodes (with none of the nodes connected by edges). If there's not too many, you could possibly just exhaustively generate all the 5-tuples of n-tuples and just filter them again.
This could be a good candidate for code golf. Someone can probably solve it in one line with something like haskell :)
afterthought: The initial filter pass can also eliminate information from the positional constraints. Not much (1/25), but still significant.
Another Python solution, this time using Python's PyKE (Python Knowledge Engine). Granted, it's more verbose than using Python's "constraint" module in the solution by #J.F.Sebastian, but it provides an interesting comparison for anybody looking into a raw knowledge engine for this type of problem.
clues.kfb
categories( POSITION, 1, 2, 3, 4, 5 ) # There are five houses.
categories( HOUSE_COLOR, blue, red, green, white, yellow ) # Each house has its own unique color.
categories( NATIONALITY, Norwegian, German, Dane, Swede, English ) # All house owners are of different nationalities.
categories( PET, birds, dog, cats, horse, zebra ) # They all have different pets.
categories( DRINK, tea, coffee, milk, beer, water ) # They all drink different drinks.
categories( SMOKE, Blend, Prince, 'Blue Master', Dunhill, 'Pall Mall' ) # They all smoke different cigarettes.
related( NATIONALITY, English, HOUSE_COLOR, red ) # The English man lives in the red house.
related( NATIONALITY, Swede, PET, dog ) # The Swede has a dog.
related( NATIONALITY, Dane, DRINK, tea ) # The Dane drinks tea.
left_of( HOUSE_COLOR, green, HOUSE_COLOR, white ) # The green house is on the left side of the white house.
related( DRINK, coffee, HOUSE_COLOR, green ) # They drink coffee in the green house.
related( SMOKE, 'Pall Mall', PET, birds ) # The man who smokes Pall Mall has birds.
related( SMOKE, Dunhill, HOUSE_COLOR, yellow ) # In the yellow house they smoke Dunhill.
related( POSITION, 3, DRINK, milk ) # In the middle house they drink milk.
related( NATIONALITY, Norwegian, POSITION, 1 ) # The Norwegian lives in the first house.
next_to( SMOKE, Blend, PET, cats ) # The man who smokes Blend lives in the house next to the house with cats.
next_to( SMOKE, Dunhill, PET, horse ) # In the house next to the house where they have a horse, they smoke Dunhill.
related( SMOKE, 'Blue Master', DRINK, beer ) # The man who smokes Blue Master drinks beer.
related( NATIONALITY, German, SMOKE, Prince ) # The German smokes Prince.
next_to( NATIONALITY, Norwegian, HOUSE_COLOR, blue ) # The Norwegian lives next to the blue house.
next_to( DRINK, water, SMOKE, Blend ) # They drink water in the house next to the house where they smoke Blend.
relations.krb
#############
# Categories
# Foreach set of categories, assert each type
categories
foreach
clues.categories($category, $thing1, $thing2, $thing3, $thing4, $thing5)
assert
clues.is_category($category, $thing1)
clues.is_category($category, $thing2)
clues.is_category($category, $thing3)
clues.is_category($category, $thing4)
clues.is_category($category, $thing5)
#########################
# Inverse Relationships
# Foreach A=1, assert 1=A
inverse_relationship_positive
foreach
clues.related($category1, $thing1, $category2, $thing2)
assert
clues.related($category2, $thing2, $category1, $thing1)
# Foreach A!1, assert 1!A
inverse_relationship_negative
foreach
clues.not_related($category1, $thing1, $category2, $thing2)
assert
clues.not_related($category2, $thing2, $category1, $thing1)
# Foreach "A beside B", assert "B beside A"
inverse_relationship_beside
foreach
clues.next_to($category1, $thing1, $category2, $thing2)
assert
clues.next_to($category2, $thing2, $category1, $thing1)
###########################
# Transitive Relationships
# Foreach A=1 and 1=a, assert A=a
transitive_positive
foreach
clues.related($category1, $thing1, $category2, $thing2)
clues.related($category2, $thing2, $category3, $thing3)
check unique($thing1, $thing2, $thing3) \
and unique($category1, $category2, $category3)
assert
clues.related($category1, $thing1, $category3, $thing3)
# Foreach A=1 and 1!a, assert A!a
transitive_negative
foreach
clues.related($category1, $thing1, $category2, $thing2)
clues.not_related($category2, $thing2, $category3, $thing3)
check unique($thing1, $thing2, $thing3) \
and unique($category1, $category2, $category3)
assert
clues.not_related($category1, $thing1, $category3, $thing3)
##########################
# Exclusive Relationships
# Foreach A=1, assert A!2 and A!3 and A!4 and A!5
if_one_related_then_others_unrelated
foreach
clues.related($category, $thing, $category_other, $thing_other)
check unique($category, $category_other)
clues.is_category($category_other, $thing_not_other)
check unique($thing, $thing_other, $thing_not_other)
assert
clues.not_related($category, $thing, $category_other, $thing_not_other)
# Foreach A!1 and A!2 and A!3 and A!4, assert A=5
if_four_unrelated_then_other_is_related
foreach
clues.not_related($category, $thing, $category_other, $thingA)
clues.not_related($category, $thing, $category_other, $thingB)
check unique($thingA, $thingB)
clues.not_related($category, $thing, $category_other, $thingC)
check unique($thingA, $thingB, $thingC)
clues.not_related($category, $thing, $category_other, $thingD)
check unique($thingA, $thingB, $thingC, $thingD)
# Find the fifth variation of category_other.
clues.is_category($category_other, $thingE)
check unique($thingA, $thingB, $thingC, $thingD, $thingE)
assert
clues.related($category, $thing, $category_other, $thingE)
###################
# Neighbors: Basic
# Foreach "A left of 1", assert "A beside 1"
expanded_relationship_beside_left
foreach
clues.left_of($category1, $thing1, $category2, $thing2)
assert
clues.next_to($category1, $thing1, $category2, $thing2)
# Foreach "A beside 1", assert A!1
unrelated_to_beside
foreach
clues.next_to($category1, $thing1, $category2, $thing2)
check unique($category1, $category2)
assert
clues.not_related($category1, $thing1, $category2, $thing2)
###################################
# Neighbors: Spatial Relationships
# Foreach "A beside B" and "A=(at-edge)", assert "B=(near-edge)"
check_next_to_either_edge
foreach
clues.related(POSITION, $position_known, $category, $thing)
check is_edge($position_known)
clues.next_to($category, $thing, $category_other, $thing_other)
clues.is_category(POSITION, $position_other)
check is_beside($position_known, $position_other)
assert
clues.related(POSITION, $position_other, $category_other, $thing_other)
# Foreach "A beside B" and "A!(near-edge)" and "B!(near-edge)", assert "A!(at-edge)"
check_too_close_to_edge
foreach
clues.next_to($category, $thing, $category_other, $thing_other)
clues.is_category(POSITION, $position_edge)
clues.is_category(POSITION, $position_near_edge)
check is_edge($position_edge) and is_beside($position_edge, $position_near_edge)
clues.not_related(POSITION, $position_near_edge, $category, $thing)
clues.not_related(POSITION, $position_near_edge, $category_other, $thing_other)
assert
clues.not_related(POSITION, $position_edge, $category, $thing)
# Foreach "A beside B" and "A!(one-side)", assert "A=(other-side)"
check_next_to_with_other_side_impossible
foreach
clues.next_to($category, $thing, $category_other, $thing_other)
clues.related(POSITION, $position_known, $category_other, $thing_other)
check not is_edge($position_known)
clues.not_related($category, $thing, POSITION, $position_one_side)
check is_beside($position_known, $position_one_side)
clues.is_category(POSITION, $position_other_side)
check is_beside($position_known, $position_other_side) \
and unique($position_known, $position_one_side, $position_other_side)
assert
clues.related($category, $thing, POSITION, $position_other_side)
# Foreach "A left of B"...
# ... and "C=(position1)" and "D=(position2)" and "E=(position3)"
# ~> assert "A=(other-position)" and "B=(other-position)+1"
left_of_and_only_two_slots_remaining
foreach
clues.left_of($category_left, $thing_left, $category_right, $thing_right)
clues.related($category_left, $thing_left_other1, POSITION, $position1)
clues.related($category_left, $thing_left_other2, POSITION, $position2)
clues.related($category_left, $thing_left_other3, POSITION, $position3)
check unique($thing_left, $thing_left_other1, $thing_left_other2, $thing_left_other3)
clues.related($category_right, $thing_right_other1, POSITION, $position1)
clues.related($category_right, $thing_right_other2, POSITION, $position2)
clues.related($category_right, $thing_right_other3, POSITION, $position3)
check unique($thing_right, $thing_right_other1, $thing_right_other2, $thing_right_other3)
clues.is_category(POSITION, $position4)
clues.is_category(POSITION, $position5)
check is_left_right($position4, $position5) \
and unique($position1, $position2, $position3, $position4, $position5)
assert
clues.related(POSITION, $position4, $category_left, $thing_left)
clues.related(POSITION, $position5, $category_right, $thing_right)
#########################
fc_extras
def unique(*args):
return len(args) == len(set(args))
def is_edge(pos):
return (pos == 1) or (pos == 5)
def is_beside(pos1, pos2):
diff = (pos1 - pos2)
return (diff == 1) or (diff == -1)
def is_left_right(pos_left, pos_right):
return (pos_right - pos_left == 1)
driver.py (actually larger, but this is the essence)
from pyke import knowledge_engine
engine = knowledge_engine.engine(__file__)
engine.activate('relations')
try:
natl = engine.prove_1_goal('clues.related(PET, zebra, NATIONALITY, $nationality)')[0].get('nationality')
except Exception, e:
natl = "Unknown"
print "== Who owns the zebra? %s ==" % natl
Sample output:
$ python driver.py
== Who owns the zebra? German ==
# Color Nationality Pet Drink Smoke
=======================================================
1 yellow Norwegian cats water Dunhill
2 blue Dane horse tea Blend
3 red English birds milk Pall Mall
4 green German zebra coffee Prince
5 white Swede dog beer Blue Master
Calculated in 1.19 seconds.
Source: https://github.com/DreadPirateShawn/pyke-who-owns-zebra
Here is an excerpt from the full solution using NSolver, posted at Einstein’s Riddle in C#:
// The green house's owner drinks coffee
Post(greenHouse.Eq(coffee));
// The person who smokes Pall Mall rears birds
Post(pallMall.Eq(birds));
// The owner of the yellow house smokes Dunhill
Post(yellowHouse.Eq(dunhill));
Here is a straightforward solution in CLP(FD) (see also clpfd):
:- use_module(library(clpfd)).
solve(ZebraOwner) :-
maplist( init_dom(1..5),
[[British, Swedish, Danish, Norwegian, German], % Nationalities
[Red, Green, Blue, White, Yellow], % Houses
[Tea, Coffee, Milk, Beer, Water], % Beverages
[PallMall, Blend, Prince, Dunhill, BlueMaster], % Cigarettes
[Dog, Birds, Cats, Horse, Zebra]]), % Pets
British #= Red, % Hint 1
Swedish #= Dog, % Hint 2
Danish #= Tea, % Hint 3
Green #= White - 1 , % Hint 4
Green #= Coffee, % Hint 5
PallMall #= Birds, % Hint 6
Yellow #= Dunhill, % Hint 7
Milk #= 3, % Hint 8
Norwegian #= 1, % Hint 9
neighbor(Blend, Cats), % Hint 10
neighbor(Horse, Dunhill), % Hint 11
BlueMaster #= Beer, % Hint 12
German #= Prince, % Hint 13
neighbor(Norwegian, Blue), % Hint 14
neighbor(Blend, Water), % Hint 15
memberchk(Zebra-ZebraOwner, [British-british, Swedish-swedish, Danish-danish,
Norwegian-norwegian, German-german]).
init_dom(R, L) :-
all_distinct(L),
L ins R.
neighbor(X, Y) :-
(X #= (Y - 1)) #\/ (X #= (Y + 1)).
Running it, produces:
3 ?- time(solve(Z)).
% 111,798 inferences, 0.016 CPU in 0.020 seconds (78% CPU, 7166493 Lips)
Z = german.
In PAIP (Chapter 11), Norvig solves the zebra puzzle using a Prolog embedded in Lisp.
ES6 (Javascript) solution
With lots of ES6 generators and a little bit of lodash. You will need Babel to run this.
var _ = require('lodash');
function canBe(house, criteria) {
for (const key of Object.keys(criteria))
if (house[key] && house[key] !== criteria[key])
return false;
return true;
}
function* thereShouldBe(criteria, street) {
for (const i of _.range(street.length))
yield* thereShouldBeAtIndex(criteria, i, street);
}
function* thereShouldBeAtIndex(criteria, index, street) {
if (canBe(street[index], criteria)) {
const newStreet = _.cloneDeep(street);
newStreet[index] = _.assign({}, street[index], criteria);
yield newStreet;
}
}
function* leftOf(critA, critB, street) {
for (const i of _.range(street.length - 1)) {
if (canBe(street[i], critA) && canBe(street[i+1], critB)) {
const newStreet = _.cloneDeep(street);
newStreet[i ] = _.assign({}, street[i ], critA);
newStreet[i+1] = _.assign({}, street[i+1], critB);
yield newStreet;
}
}
}
function* nextTo(critA, critB, street) {
yield* leftOf(critA, critB, street);
yield* leftOf(critB, critA, street);
}
const street = [{}, {}, {}, {}, {}]; // five houses
// Btw: it turns out we don't need uniqueness constraint.
const constraints = [
s => thereShouldBe({nation: 'English', color: 'red'}, s),
s => thereShouldBe({nation: 'Swede', animal: 'dog'}, s),
s => thereShouldBe({nation: 'Dane', drink: 'tea'}, s),
s => leftOf({color: 'green'}, {color: 'white'}, s),
s => thereShouldBe({drink: 'coffee', color: 'green'}, s),
s => thereShouldBe({cigarettes: 'PallMall', animal: 'birds'}, s),
s => thereShouldBe({color: 'yellow', cigarettes: 'Dunhill'}, s),
s => thereShouldBeAtIndex({drink: 'milk'}, 2, s),
s => thereShouldBeAtIndex({nation: 'Norwegian'}, 0, s),
s => nextTo({cigarettes: 'Blend'}, {animal: 'cats'}, s),
s => nextTo({animal: 'horse'}, {cigarettes: 'Dunhill'}, s),
s => thereShouldBe({cigarettes: 'BlueMaster', drink: 'beer'}, s),
s => thereShouldBe({nation: 'German', cigarettes: 'Prince'}, s),
s => nextTo({nation: 'Norwegian'}, {color: 'blue'}, s),
s => nextTo({drink: 'water'}, {cigarettes: 'Blend'}, s),
s => thereShouldBe({animal: 'zebra'}, s), // should be somewhere
];
function* findSolution(remainingConstraints, street) {
if (remainingConstraints.length === 0)
yield street;
else
for (const newStreet of _.head(remainingConstraints)(street))
yield* findSolution(_.tail(remainingConstraints), newStreet);
}
for (const streetSolution of findSolution(constraints, street)) {
console.log(streetSolution);
}
Result:
[ { color: 'yellow',
cigarettes: 'Dunhill',
nation: 'Norwegian',
animal: 'cats',
drink: 'water' },
{ nation: 'Dane',
drink: 'tea',
cigarettes: 'Blend',
animal: 'horse',
color: 'blue' },
{ nation: 'English',
color: 'red',
cigarettes: 'PallMall',
animal: 'birds',
drink: 'milk' },
{ color: 'green',
drink: 'coffee',
nation: 'German',
cigarettes: 'Prince',
animal: 'zebra' },
{ nation: 'Swede',
animal: 'dog',
color: 'white',
cigarettes: 'BlueMaster',
drink: 'beer' } ]
Run time is around 2.5s for me, but this can be improved a lot by changing the order of rules. I decided to keep the original order for clarity.
Thanks, this was a cool challenge!
This is really a constraint solving problem. You can do it with a generalized kind of constraint propagation in logic-programming like languages. We have a demo specifically for the Zebra problem in the ALE (attribute logic engine) system:
http://www.cs.toronto.edu/~gpenn/ale.html
Here's the link to the coding of a simplified Zebra puzzle:
http://www.cs.toronto.edu/~gpenn/ale/files/grammars/baby.pl
To do this efficiently is another matter.
The easiest way to solve such problems programmatically is to use nested loops over all permutations and check to see if the result satisfies the predicates in the question. Many of the predicates can be hoisted from the inner loop to outer loops in order to dramatically reduce the computational complexity until the answer can be computed in a reasonable time.
Here is a simple F# solution derived from an article in the F# Journal:
let rec distribute y xs =
match xs with
| [] -> [[y]]
| x::xs -> (y::x::xs)::[for xs in distribute y xs -> x::xs]
let rec permute xs =
match xs with
| [] | [_] as xs -> [xs]
| x::xs -> List.collect (distribute x) (permute xs)
let find xs x = List.findIndex ((=) x) xs + 1
let eq xs x ys y = find xs x = find ys y
let nextTo xs x ys y = abs(find xs x - find ys y) = 1
let nations = ["British"; "Swedish"; "Danish"; "Norwegian"; "German"]
let houses = ["Red"; "Green"; "Blue"; "White"; "Yellow"]
let drinks = ["Milk"; "Coffee"; "Water"; "Beer"; "Tea"]
let smokes = ["Blend"; "Prince"; "Blue Master"; "Dunhill"; "Pall Mall"]
let pets = ["Dog"; "Cat"; "Zebra"; "Horse"; "Bird"]
[ for nations in permute nations do
if find nations "Norwegian" = 1 then
for houses in permute houses do
if eq nations "British" houses "Red" &&
find houses "Green" = find houses "White"-1 &&
nextTo nations "Norwegian" houses "Blue" then
for drinks in permute drinks do
if eq nations "Danish" drinks "Tea" &&
eq houses "Green" drinks "Coffee" &&
3 = find drinks "Milk" then
for smokes in permute smokes do
if eq houses "Yellow" smokes "Dunhill" &&
eq smokes "Blue Master" drinks "Beer" &&
eq nations "German" smokes "Prince" &&
nextTo smokes "Blend" drinks "Water" then
for pets in permute pets do
if eq nations "Swedish" pets "Dog" &&
eq smokes "Pall Mall" pets "Bird" &&
nextTo pets "Cat" smokes "Blend" &&
nextTo pets "Horse" smokes "Dunhill" then
yield nations, houses, drinks, smokes, pets ]
The output obtained in 9ms is:
val it :
(string list * string list * string list * string list * string list) list =
[(["Norwegian"; "Danish"; "British"; "German"; "Swedish"],
["Yellow"; "Blue"; "Red"; "Green"; "White"],
["Water"; "Tea"; "Milk"; "Coffee"; "Beer"],
["Dunhill"; "Blend"; "Pall Mall"; "Prince"; "Blue Master"],
["Cat"; "Horse"; "Bird"; "Zebra"; "Dog"])]
This is a MiniZinc solution to the zebra puzzle as defined in Wikipedia:
include "globals.mzn";
% Zebra puzzle
int: nc = 5;
% Colors
int: red = 1;
int: green = 2;
int: ivory = 3;
int: yellow = 4;
int: blue = 5;
array[1..nc] of var 1..nc:color;
constraint alldifferent([color[i] | i in 1..nc]);
% Nationalities
int: eng = 1;
int: spa = 2;
int: ukr = 3;
int: nor = 4;
int: jap = 5;
array[1..nc] of var 1..nc:nationality;
constraint alldifferent([nationality[i] | i in 1..nc]);
% Pets
int: dog = 1;
int: snail = 2;
int: fox = 3;
int: horse = 4;
int: zebra = 5;
array[1..nc] of var 1..nc:pet;
constraint alldifferent([pet[i] | i in 1..nc]);
% Drinks
int: coffee = 1;
int: tea = 2;
int: milk = 3;
int: orange = 4;
int: water = 5;
array[1..nc] of var 1..nc:drink;
constraint alldifferent([drink[i] | i in 1..nc]);
% Smokes
int: oldgold = 1;
int: kools = 2;
int: chesterfields = 3;
int: luckystrike = 4;
int: parliaments = 5;
array[1..nc] of var 1..nc:smoke;
constraint alldifferent([smoke[i] | i in 1..nc]);
% The Englishman lives in the red house.
constraint forall ([nationality[i] == eng <-> color[i] == red | i in 1..nc]);
% The Spaniard owns the dog.
constraint forall ([nationality[i] == spa <-> pet[i] == dog | i in 1..nc]);
% Coffee is drunk in the green house.
constraint forall ([color[i] == green <-> drink[i] == coffee | i in 1..nc]);
% The Ukrainian drinks tea.
constraint forall ([nationality[i] == ukr <-> drink[i] == tea | i in 1..nc]);
% The green house is immediately to the right of the ivory house.
constraint forall ([color[i] == ivory -> if i<nc then color[i+1] == green else false endif | i in 1..nc]);
% The Old Gold smoker owns snails.
constraint forall ([smoke[i] == oldgold <-> pet[i] == snail | i in 1..nc]);
% Kools are smoked in the yellow house.
constraint forall ([smoke[i] == kools <-> color[i] == yellow | i in 1..nc]);
% Milk is drunk in the middle house.
constraint drink[3] == milk;
% The Norwegian lives in the first house.
constraint nationality[1] == nor;
% The man who smokes Chesterfields lives in the house next to the man with the fox.
constraint forall ([smoke[i] == chesterfields -> (if i>1 then pet[i-1] == fox else false endif \/ if i<nc then pet[i+1] == fox else false endif) | i in 1..nc]);
% Kools are smoked in the house next to the house where the horse is kept.
constraint forall ([smoke[i] == kools -> (if i>1 then pet[i-1] == horse else false endif \/ if i<nc then pet[i+1] == horse else false endif)| i in 1..nc]);
%The Lucky Strike smoker drinks orange juice.
constraint forall ([smoke[i] == luckystrike <-> drink[i] == orange | i in 1..nc]);
% The Japanese smokes Parliaments.
constraint forall ([nationality[i] == jap <-> smoke[i] == parliaments | i in 1..nc]);
% The Norwegian lives next to the blue house.
constraint forall ([color[i] == blue -> (if i > 1 then nationality[i-1] == nor else false endif \/ if i<nc then nationality[i+1] == nor else false endif) | i in 1..nc]);
solve satisfy;
Solution:
Compiling zebra.mzn
Running zebra.mzn
color = array1d(1..5 ,[4, 5, 1, 3, 2]);
nationality = array1d(1..5 ,[4, 3, 1, 2, 5]);
pet = array1d(1..5 ,[3, 4, 2, 1, 5]);
drink = array1d(1..5 ,[5, 2, 3, 4, 1]);
smoke = array1d(1..5 ,[2, 3, 1, 4, 5]);
----------
Finished in 47msec
The Microsoft Solver Foundation example from:
https://msdn.microsoft.com/en-us/library/ff525831%28v=vs.93%29.aspx?f=255&MSPPError=-2147217396
delegate CspTerm NamedTerm(string name);
public static void Zebra() {
ConstraintSystem S = ConstraintSystem.CreateSolver();
var termList = new List<KeyValuePair<CspTerm, string>>();
NamedTerm House = delegate(string name) {
CspTerm x = S.CreateVariable(S.CreateIntegerInterval(1, 5), name);
termList.Add(new KeyValuePair<CspTerm, string>(x, name));
return x;
};
CspTerm English = House("English"), Spanish = House("Spanish"),
Japanese = House("Japanese"), Italian = House("Italian"),
Norwegian = House("Norwegian");
CspTerm red = House("red"), green = House("green"),
white = House("white"),
blue = House("blue"), yellow = House("yellow");
CspTerm dog = House("dog"), snails = House("snails"),
fox = House("fox"),
horse = House("horse"), zebra = House("zebra");
CspTerm painter = House("painter"), sculptor = House("sculptor"),
diplomat = House("diplomat"), violinist = House("violinist"),
doctor = House("doctor");
CspTerm tea = House("tea"), coffee = House("coffee"),
milk = House("milk"),
juice = House("juice"), water = House("water");
S.AddConstraints(
S.Unequal(English, Spanish, Japanese, Italian, Norwegian),
S.Unequal(red, green, white, blue, yellow),
S.Unequal(dog, snails, fox, horse, zebra),
S.Unequal(painter, sculptor, diplomat, violinist, doctor),
S.Unequal(tea, coffee, milk, juice, water),
S.Equal(English, red),
S.Equal(Spanish, dog),
S.Equal(Japanese, painter),
S.Equal(Italian, tea),
S.Equal(1, Norwegian),
S.Equal(green, coffee),
S.Equal(1, green - white),
S.Equal(sculptor, snails),
S.Equal(diplomat, yellow),
S.Equal(3, milk),
S.Equal(1, S.Abs(Norwegian - blue)),
S.Equal(violinist, juice),
S.Equal(1, S.Abs(fox - doctor)),
S.Equal(1, S.Abs(horse - diplomat))
);
bool unsolved = true;
ConstraintSolverSolution soln = S.Solve();
while (soln.HasFoundSolution) {
unsolved = false;
System.Console.WriteLine("solved.");
StringBuilder[] houses = new StringBuilder[5];
for (int i = 0; i < 5; i++)
houses[i] = new StringBuilder(i.ToString());
foreach (KeyValuePair<CspTerm, string> kvp in termList) {
string item = kvp.Value;
object house;
if (!soln.TryGetValue(kvp.Key, out house))
throw new InvalidProgramException(
"can't find a Term in the solution: " + item);
houses[(int)house - 1].Append(", ");
houses[(int)house - 1].Append(item);
}
foreach (StringBuilder house in houses) {
System.Console.WriteLine(house);
}
soln.GetNext();
}
if (unsolved)
System.Console.WriteLine("No solution found.");
else
System.Console.WriteLine(
"Expected: the Norwegian drinking water and the Japanese with the zebra.");
}
One example of a programmatic solution (originally written for a similar question), can be found here: https://puzzle-solvers.readthedocs.io/en/latest/
I implemented a matrix of relationships between the classes, which gets updated as you enter the constraints. The API centers on a Solver class, which you initialize with the categories and labels. You then call methods like adjecent_to and match on to set up the relationships.
The docs have a fairly thorough explanation of the underlying logic. The exact puzzle you describe is one of the demos. To answer your literal question, here is what the demo looks like:
positions = [1, 2, 3, 4, 5]
nationalities = [
'Englishman', 'Spaniard', 'Ukrainian', 'Norwegian', 'Japanese'
]
colors = ['red', 'green', 'ivory', 'yellow', 'blue']
pets = ['dog', 'snails', 'fox', 'horse', 'ZEBRA']
drinks = ['coffee', 'tea', 'milk', 'orange juice', 'WATER']
cigarettes = [
'Old Gold', 'Kools', 'Chesterfields', 'Lucky Strikes', 'Parliaments'
]
problem = {
'position': positions,
'nationality': nationalities,
'color': colors,
'pet': pets,
'drink': drinks,
'cigarette': cigarettes,
}
solver = Solver(problem)
if __name__ == '__main__':
solver.match('Englishman', 'red')
solver.match('Spaniard', 'dog')
solver.match('coffee', 'green')
solver.match('Ukrainian', 'tea')
solver.greater_than('green', 'ivory', 'position', 1)
solver.match('Old Gold', 'snails')
solver.match('Kools', 'yellow')
solver.match('milk', 3)
solver.match('Norwegian', 1)
solver.adjacent_to('Chesterfields', 'fox', 'position')
solver.adjacent_to('Kools', 'horse', 'position')
solver.match('Lucky Strikes', 'orange juice')
solver.match('Japanese', 'Parliaments')
solver.adjacent_to('Norwegian', 'blue', 'position')
solver.draw(show=False, title=f'After Rules: {solver.edges} Edges')
print(f'Solved? {solver.solved}')
print(f'{solver.category_for("ZEBRA", "nationality")} owns the ZEBRA')
print(f'{solver.category_for("WATER", "nationality")} drinks WATER')
The nice thing about this code is that it's something one might write overnight, and not a really well thought-out production package, yet it still does the job.
Related
I have original dataset in json format. Let's load it in R.
library("rjson")
setwd("mydir")
getwd()
json_data <- fromJSON(paste(readLines("N1.json"), collapse=""))
uu <- unlist(json_data)
uutext <- uu[names(uu) == "text"]
And I have another dataset mydata2
mydata=read.csv(path to data/words)
I need to find the words in mydata2, only which are present in messages in json file. And then write this messages into the new document, "xyz.txt" How to do it?
chalk indirect pick reaction team skip pumpkin surprise bless ignorance
1 time patient road extent decade cemetery staircase monarch bubble abbey
2 service conglomerate banish pan friendly position tight highlight rice disappear
3 write swear break tire jam neutral momentum requirement relationship matrix
4 inspire dose jump promote trace latest absolute adjust joystick habit
5 wrong behave claim dedicate threat sell particle statement teach lamb
6 eye tissue prescription problem secretion revenge barrel beard mechanism platform
7 forest kick face wisecrack uncertainty ratio complain doubt reflection realism
8 total fee debate hall soft smart sip ritual pill category
9 contain headline lump absorption superintendent digital increase key banner second
i mean
chalk -1 number1 indirect -2 number2
template
Word1-1 number1-1; Word1-2 number 1-2; …; Word 1-10 number 1-10
Word2-1 number2-1; Word2-2 number 2-2; …; Word 2-10 number 2-10
Next time pls include real data. Simplified model:
library(data.table)
word = c("test","meh","blah")
jsonF = c("let's do test", "blah is right", "test blah", "test test")
outp <- list()
for (i in 1:length(word)) {
outp[[i]] = as.data.frame(grep(word[i],jsonF,v=T,fixed=T)) # possibly, ignore.case=T
}
qq = rbindlist(outp)
qq = unique(qq)
print(qq)
1: let's do test
2: test blah
3: test test
4: blah is right
Edit: quick and dirty paste/collapse:
library(data.table)
x = LETTERS[1:10]
y = LETTERS[11:20]
df = rbind(x,y)
L = list()
for (i in 1:nrow(df)) {
L[i] = paste0(df[i,],"-",seq(1,10)," ",i,"-",seq(1,10),collapse="; ")
}
Fin = cbind(L)
View(Fin)
Gives:
> Fin
L
[1,] "A-1 1-1; B-2 1-2; C-3 1-3; D-4 1-4; E-5 1-5; F-6 1-6; G-7 1-7; H-8 1-8; I-9 1-9; J-10 1-10"
[2,] "K-1 2-1; L-2 2-2; M-3 2-3; N-4 2-4; O-5 2-5; P-6 2-6; Q-7 2-7; R-8 2-8; S-9 2-9; T-10 2-10"
I am running a simple sentence disambiguation test. But the synset returned by nltk Lesk for the word 'cat' in the sentence "The cat likes milk" is 'kat.n.01', synsetid=3608870.
(n) kat, khat, qat, quat, cat, Arabian tea, African tea (the leaves of the shrub Catha edulis which are chewed like tobacco or used to make tea; has the effect of a euphoric stimulant) "in Yemen kat is used daily by 85% of adults"
This is a simple phrase and yet the disambiguation task fails.
And this is happening for many words in a set containing more than one sentence, for example in my test sentences, I would expect 'dog' to be disambiguated as 'domestic dog' but Lesk gives me 'pawl' (a hinged catch that fits into a notch of a ratchet to move a wheel forward or prevent it from moving backward)
Is it related to the size of the training set which is in my test only few sentences?
Here is my test code:
def test_lesk():
words = get_sample_words()
print(words)
tagger = PerceptronTagger()
tags = tagger.tag(words)
print (tags[:5])
for word, tag in tags:
pos = get_wordnet_pos(tag)
if pos is None:
continue
print("word=%s,tag=%s,pos=%s" %(word, tag, pos))
synset = lesk(words, word, pos)
if synset is None:
print('No synsetid for word=%s' %word)
else:
print('word=%s, synsetname=%s, synsetid=%d' %(word,synset.name(), synset.offset()))
This question already has answers here:
How to test multiple variables for equality against a single value?
(31 answers)
Closed 6 years ago.
I'm having a bit of trouble with my class functions. On my class I have 3 different functions but whenever I call one of the functions outside of the class, it only ever calls the first one despite me typing in the correct function name.
This is the class below with the different functions, although I have only included two as I don't want you to have to search through lots of code.
class mage(baseclass):
def __init__(self, name, level, attack, defence, hp):
baseclass.__init__(self, name, level, hp)
self.attack = attack
self.defence = defence
def __str__(self):
return "You are now a Mage, your new stats are:\n Level: {0}\n Attack: {1}\n Defence: {2}\n HP: {3}".format(self.level, self.attack, self.defence, self.hp)
def flamevortex(self, x, y, z):
print("You used Flame Vortex")
time.sleep(1.5)
damageofmove = 3
damagedone = damageofmove*y
damagedoneafterdefence = damagedone - z
x = x - damagedoneafterdefence
print("The monster's health is now " + str(x))
time.sleep(1.5)
return x
def lightningbolt(self, x, y, z):
print("You used Lightning Bolt")
time.sleep(1.5)
damageofmove = 3
damagedone = damageofmove*y
damagedoneafterdefence = damagedone - z
x = x - damagedoneafterdefence
print("The monster's health is now " + str(x))
time.sleep(1.5)
return x
This is the place where I am calling the functions:
if Userattack.upper() == "FLAMEVORTEX" or "FLAME VORTEX":
monster1.hp = p1.flamevortex(monster1.hp, p1.attack, monster1.defence)
if chosenmove == monsterattacks[0]:
p1.hp = monsterlasersword(p1.hp)
elif chosenmove == monsterattacks[1]:
p1.hp = monsterswipe(p1.hp)
elif chosenmove == monsterattacks[2]:
monster1.hp = monsterregen(monster1.hp)
time.sleep(1.5)
print("After the monster's attacks, your hp is now " + str(p1.hp))
elif Userattack.upper() == "LIGHTNINGBOLT" or "LIGHTNING BOLT":
monster1.hp = p1.lightningbolt(monster1.hp, p1.attack, monster1.defence)
if chosenmove == monsterattacks[0]:
p1.hp = monsterlasersword(p1.hp)
elif chosenmove == monsterattacks[1]:
p1.hp = monsterswipe(p1.hp)
elif chosenmove == monsterattacks[2]:
monster1.hp = monsterregen(monster1.hp)
time.sleep(1.5)
print("After the monster's attacks, your hp is now " + str(p1.hp))
No matter what the user inputs, it only ever calls the first function.
I know this is a lot to process and appreciate any help. Thanks
if Userattack.upper() == "FLAMEVORTEX" or "FLAME VORTEX": means is userattack.upper() equal to "FLAMEVORTEX", or does the string "FLAME VORTEX" have True value.
Now since empty strings are False and non-empty strings are True, Userattack.upper() == "FLAMEVORTEX" or "FLAME VORTEX" is always True, and that's not what you meant.
Try: Userattack.upper() == "FLAMEVORTEX" or Userattack.upper()=="FLAME VORTEX"
I am trying to write a script that will iterate through a CSV file in Ruby that will produce a multi-level unordered list.
The code I have now doesn't cut off the <ul> in the right spots. The output just continues to new levels.
Here is the Ruby code:
require 'csv'
col_data = []
CSV.foreach("primary_NAICS_code.txt") {|row| col_data << row}
begin
file = File.open("primary_NAICS_code.html", "w")
file.write("<ul>\n")
depth = 1
col_data.each do |row|
indentation, (text,*) = row.slice_before(String).to_a
if indentation.length > depth
file.write("<ul>\n")
elsif indentation.length < depth
file.write("</ul>\n")
end
if text.index(/^\d{2}[:]/)
file.write("<li>" +text+ "</li></ul>\n")
else
file.write("<li>" +text+ "</li>\n")
end
depth = indentation.length
end
file.write("</ul>\n")
rescue IOError => e
puts e
ensure
file.close unless file == nil
end
This is the output I'm getting:
11: Agriculture, Forestry, Fishing and Hunting
111: Crop Production
111110: Soybean Farming
111120: Oilseed (except Soybean) Farming
111130: Dry Pea and Bean Farming
.
.
.
112: Animal Production
112111: Beef Cattle Ranching and Farming
112112: Cattle Feedlots
.
.
.
21: Mining<~~(there should be a </ul> here)
211: Oil and Gas Extraction
211111: Crude Petroleum and Natural Gas Extraction
211112: Natural Gas Liquid Extraction
And the CSV file:
,"11: Agriculture, Forestry, Fishing and Hunting",,
,,"111: Crop Production",
,,,"111110: Soybean Farming"
,,,"111120: Oilseed (except Soybean) Farming"
,,,"111130: Dry Pea and Bean Farming"
,,,"111140: Wheat Farming"
,,,"111150: Corn Farming"
,,,"111160: Rice Farming"
,,,"111191: Oilseed and Grain Combination Farming"
,,,"111199: All Other Grain Farming"
,,,"111211: Potato Farming"
,,,"111219: Other Vegetable (except Potato) and Melon Farming"
,,"112: Animal Production",
,,,"112111: Beef Cattle Ranching and Farming"
,,,"112112: Cattle Feedlots"
,,,"112120: Dairy Cattle and Milk Production"
,,,"112130: Dual-Purpose Cattle Ranching and Farming"
,,,"112210: Hog and Pig Farming"
,,"113: Forestry and Logging",
,,,"113110: Timber Tract Operations"
,,,"113210: Forest Nurseries and Gathering of Forest Products"
,,,"113310: Logging"
,,"114: Fishing, Hunting and Trapping",
,,,"114111: Finfish Fishing"
,,,"114112: Shellfish Fishing"
,,,"114119: Other Marine Fishing"
,,,"114210: Hunting and Trapping"
,,"115: Support Activities for Agriculture and Forestry",
,"21: Mining",,
,,"211: Oil and Gas Extraction",
,,,"211111: Crude Petroleum and Natural Gas Extraction"
,,,"211112: Natural Gas Liquid Extraction"
,,"212: Mining (except Oil and Gas)",
.
.
.
,"54: Professional, Scientific, and Technical Services",,
,,"541: Professional, Scientific, and Technical Services",
,,,"541110: Offices of Lawyers"
require 'csv'
CSV_FILE = 'data.csv'
HTML_FILE = 'data.html'
# helper class for nodes in a tree
class Node < Struct.new(:parent, :text, :children)
def initialize(parent, text)
super
self.children = []
parent.children << self if parent
self
end
def level
root? ? 0 : parent.level + 1
end
def to_html
"".tap do |html|
html << "#{tabs(level)}<li>#{text}" unless root?
html << "#{tabs(level)}<ul>#{children.map(&:to_html).join}</ul>" if children.any?
html << "</li>" unless root?
end
end
private
def root?
parent.nil?
end
def tabs(level)
"\n" + "\t" * level
end
end
# init tree
tree = Node.new(nil, :root)
node = tree
# process csv file
CSV.read(CSV_FILE).map do |row|
levels, (text, _) = row.slice_before(String).to_a
level = levels.size
parent = node if level > node.level
parent = node.parent if level == node.level
parent = node.parent.parent if level < node.level
node = Node.new(parent, text)
end
# write output file
File.open(HTML_FILE, 'w') do |file|
file << "<!DOCTYPE HTML><html><head><title>CSV</title></head><body>"
file << tree.to_html
file << "</body></html>"
end
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
The problem/comic in question: http://xkcd.com/287/
I'm not sure this is the best way to do it, but here's what I've come up with so far. I'm using CFML, but it should be readable by anyone.
<cffunction name="testCombo" returntype="boolean">
<cfargument name="currentCombo" type="string" required="true" />
<cfargument name="currentTotal" type="numeric" required="true" />
<cfargument name="apps" type="array" required="true" />
<cfset var a = 0 />
<cfset var found = false />
<cfloop from="1" to="#arrayLen(arguments.apps)#" index="a">
<cfset arguments.currentCombo = listAppend(arguments.currentCombo, arguments.apps[a].name) />
<cfset arguments.currentTotal = arguments.currentTotal + arguments.apps[a].cost />
<cfif arguments.currentTotal eq 15.05>
<!--- print current combo --->
<cfoutput><strong>#arguments.currentCombo# = 15.05</strong></cfoutput><br />
<cfreturn true />
<cfelseif arguments.currentTotal gt 15.05>
<cfoutput>#arguments.currentCombo# > 15.05 (aborting)</cfoutput><br />
<cfreturn false />
<cfelse>
<!--- less than 15.05 --->
<cfoutput>#arguments.currentCombo# < 15.05 (traversing)</cfoutput><br />
<cfset found = testCombo(arguments.currentCombo, arguments.currentTotal, arguments.apps) />
</cfif>
</cfloop>
</cffunction>
<cfset mf = {name="Mixed Fruit", cost=2.15} />
<cfset ff = {name="French Fries", cost=2.75} />
<cfset ss = {name="side salad", cost=3.35} />
<cfset hw = {name="hot wings", cost=3.55} />
<cfset ms = {name="moz sticks", cost=4.20} />
<cfset sp = {name="sampler plate", cost=5.80} />
<cfset apps = [ mf, ff, ss, hw, ms, sp ] />
<cfloop from="1" to="6" index="b">
<cfoutput>#testCombo(apps[b].name, apps[b].cost, apps)#</cfoutput>
</cfloop>
The above code tells me that the only combination that adds up to $15.05 is 7 orders of Mixed Fruit, and it takes 232 executions of my testCombo function to complete.
Is there a better algorithm to come to the correct solution? Did I come to the correct solution?
It gives all the permutations of the solutions, but I think I'm beating everyone else for code size.
item(X) :- member(X,[215, 275, 335, 355, 420, 580]).
solution([X|Y], Z) :- item(X), plus(S, X, Z), Z >= 0, solution(Y, S).
solution([], 0).
Solution with swiprolog:
?- solution(X, 1505).
X = [215, 215, 215, 215, 215, 215, 215] ;
X = [215, 355, 355, 580] ;
X = [215, 355, 580, 355] ;
X = [215, 580, 355, 355] ;
X = [355, 215, 355, 580] ;
X = [355, 215, 580, 355] ;
X = [355, 355, 215, 580] ;
X = [355, 355, 580, 215] ;
X = [355, 580, 215, 355] ;
X = [355, 580, 355, 215] ;
X = [580, 215, 355, 355] ;
X = [580, 355, 215, 355] ;
X = [580, 355, 355, 215] ;
No
The point about an NP-complete problem is not that it's tricky on a small data set, but that the amount of work to solve it grows at a rate greater than polynomial, i.e. there is no O(n^x) algorithm.
If the time complexity is O(n!), as in (I believe) the two problems mentioned above, that is in NP.
Isn't it more elegant with recursion (in Perl)?
#!/usr/bin/perl
use strict;
use warnings;
my #weights = (2.15, 2.75, 3.35, 3.55, 4.20, 5.80);
my $total = 0;
my #order = ();
iterate($total, #order);
sub iterate
{
my ($total, #order) = #_;
foreach my $w (#weights)
{
if ($total+$w == 15.05)
{
print join (', ', (#order, $w)), "\n";
}
if ($total+$w < 15.05)
{
iterate($total+$w, (#order, $w));
}
}
}
Output
marco#unimatrix-01:~$ ./xkcd-knapsack.pl
2.15, 2.15, 2.15, 2.15, 2.15, 2.15, 2.15
2.15, 3.55, 3.55, 5.8
2.15, 3.55, 5.8, 3.55
2.15, 5.8, 3.55, 3.55
3.55, 2.15, 3.55, 5.8
3.55, 2.15, 5.8, 3.55
3.55, 3.55, 2.15, 5.8
3.55, 5.8, 2.15, 3.55
5.8, 2.15, 3.55, 3.55
5.8, 3.55, 2.15, 3.55
Even though knapsack is NP Complete, it is a very special problem: the usual dynamic program for it is in fact excellent (http://en.wikipedia.org/wiki/Knapsack_problem)
And if you do the correct analysis, it turns out that it is O(nW), n being the # of items, and W being the target number. The problem is when you have to DP over a large W, that's when we get the NP behaviour. But for the most part, knapsack is reasonably well behaved and you can solve really large instances of it with no problems. As far as NP complete problems go, knapsack is one of the easiest.
Here is the solution using constraint.py
>>> from constraint import *
>>> problem = Problem()
>>> menu = {'mixed-fruit': 2.15,
... 'french-fries': 2.75,
... 'side-salad': 3.35,
... 'hot-wings': 3.55,
... 'mozarella-sticks': 4.20,
... 'sampler-plate': 5.80}
>>> for appetizer in menu:
... problem.addVariable( appetizer, [ menu[appetizer] * i for i in range( 8 )] )
>>> problem.addConstraint(ExactSumConstraint(15.05))
>>> problem.getSolutions()
[{'side-salad': 0.0, 'french-fries': 0.0, 'sampler-plate': 5.7999999999999998, 'mixed-fruit': 2.1499999999999999, 'mozarella-sticks': 0.0, 'hot-wings': 7.0999999999999996},
{'side-salad': 0.0, 'french-fries': 0.0, 'sampler-plate': 0.0, 'mixed-fruit': 15.049999999999999, 'mozarella-sticks': 0.0, 'hot-wings': 0.0}]
So the solution is to order a sampler plate, a mixed fruit, and 2 orders of hot wings, or order 7 mixed-fruits.
Here's a solution with F#:
#light
type Appetizer = { name : string; cost : int }
let menu = [
{name="fruit"; cost=215}
{name="fries"; cost=275}
{name="salad"; cost=335}
{name="wings"; cost=355}
{name="moz sticks"; cost=420}
{name="sampler"; cost=580}
]
// Choose: list<Appetizer> -> list<Appetizer> -> int -> list<list<Appetizer>>
let rec Choose allowedMenu pickedSoFar remainingMoney =
if remainingMoney = 0 then
// solved it, return this solution
[ pickedSoFar ]
else
// there's more to spend
[match allowedMenu with
| [] -> yield! [] // no more items to choose, no solutions this branch
| item :: rest ->
if item.cost <= remainingMoney then
// if first allowed is within budget, pick it and recurse
yield! Choose allowedMenu (item :: pickedSoFar) (remainingMoney - item.cost)
// regardless, also skip ever picking more of that first item and recurse
yield! Choose rest pickedSoFar remainingMoney]
let solutions = Choose menu [] 1505
printfn "%d solutions:" solutions.Length
solutions |> List.iter (fun solution ->
solution |> List.iter (fun item -> printf "%s, " item.name)
printfn ""
)
(*
2 solutions:
fruit, fruit, fruit, fruit, fruit, fruit, fruit,
sampler, wings, wings, fruit,
*)
Read up on the Knapsack Problem.
You've got all the correct combinations now, but you're still checking many more than you need to (as evidenced by the many permutations your result shows). Also, you're omitting the last item that hits the 15.05 mark.
I have a PHP version that does 209 iterations of the recursive call (it does 2012 if I get all permutations). You can reduce your count if right before the end of your loop, you pull out the item you just checked.
I don't know CF syntax, but it will be something like this:
<cfelse>
<!--- less than 15.50 --->
<!--<cfoutput>#arguments.currentCombo# < 15.05 (traversing)</cfoutput><br />-->
<cfset found = testCombo(CC, CT, arguments.apps) />
------- remove the item from the apps array that was just checked here ------
</cfif>
</cfloop>
EDIT: For reference, here's my PHP version:
<?
function rc($total, $string, $m) {
global $c;
$m2 = $m;
$c++;
foreach($m as $i=>$p) {
if ($total-$p == 0) {
print "$string $i\n";
return;
}
if ($total-$p > 0) {
rc($total-$p, $string . " " . $i, $m2);
}
unset($m2[$i]);
}
}
$c = 0;
$m = array("mf"=>215, "ff"=>275, "ss"=>335, "hw"=>355, "ms"=>420, "sp"=>580);
rc(1505, "", $m);
print $c;
?>
Output
mf mf mf mf mf mf mf
mf hw hw sp
209
EDIT 2:
Since explaining why you can remove the elements will take a little more than I could fit in a comment, I'm adding it here.
Basically, each recursion will find all combinations that include the currently search element (e.g., the first step will find everything including at least one mixed fruit). The easiest way to understand it is to trace the execution, but since that will take a lot of space, I'll do it as if the target was 6.45.
MF (2.15)
MF (4.30)
MF (6.45) *
FF (7.05) X
SS (7.65) X
...
[MF removed for depth 2]
FF (4.90)
[checking MF now would be redundant since we checked MF/MF/FF previously]
FF (7.65) X
...
[FF removed for depth 2]
SS (5.50)
...
[MF removed for depth 1]
At this point, we've checked every combination that includes any mixed fruit, so there's no need to check for mixed fruit again. You can use the same logic to prune the array at each of the deeper recursions as well.
Tracing through it like this actually suggested another slight time saver -- knowing the prices are sorted from low to high means that we don't need to keep checking items once we go over the target.
I would make one suggestion about the design of the algorithm itself (which is how I interpreted the intent of your original question). Here is a fragment of the solution I wrote:
....
private void findAndReportSolutions(
int target, // goal to be achieved
int balance, // amount of goal remaining
int index // menu item to try next
) {
++calls;
if (balance == 0) {
reportSolution(target);
return; // no addition to perfect order is possible
}
if (index == items.length) {
++falls;
return; // ran out of menu items without finding solution
}
final int price = items[index].price;
if (balance < price) {
return; // all remaining items cost too much
}
int maxCount = balance / price; // max uses for this item
for (int n = maxCount; 0 <= n; --n) { // loop for this item, recur for others
++loops;
counts[index] = n;
findAndReportSolutions(
target, balance - n * price, index + 1
);
}
}
public void reportSolutionsFor(int target) {
counts = new int[items.length];
calls = loops = falls = 0;
findAndReportSolutions(target, target, 0);
ps.printf("%d calls, %d loops, %d falls%n", calls, loops, falls);
}
public static void main(String[] args) {
MenuItem[] items = {
new MenuItem("mixed fruit", 215),
new MenuItem("french fries", 275),
new MenuItem("side salad", 335),
new MenuItem("hot wings", 355),
new MenuItem("mozzarella sticks", 420),
new MenuItem("sampler plate", 580),
};
Solver solver = new Solver(items);
solver.reportSolutionsFor(1505);
}
...
(Note that the constructor does sort the menu items by increasing price, to enable the constant-time early termination when the remaining balance is smaller than any remaining menu item.)
The output for a sample run is:
7 mixed fruit (1505) = 1505
1 mixed fruit (215) + 2 hot wings (710) + 1 sampler plate (580) = 1505
348 calls, 347 loops, 79 falls
The design suggestion I want to highlight is that in the above code, each nested (recursive) call of findAndReportSolution(...) deals with the quantity of exactly one menu item, identified by the index argument. In other words, the recursive nesting parallels the behavior of an in-line set of nested loops; the outermost counts possible uses of the first menu item, the next in counts the uses of the second menu item, etc. (Except, of course, the use of recursion liberates the code from dependence on a specific number of menu items!)
I suggest that this makes it easier to design the code, and easier to understand what each invocation is doing (accounting for all possible uses of a specific item, delegating the remainder of the menu to subordinate calls). It also avoids the combinatorial explosion of producing all arrangements of a multiple-item solution (as in the second line of the above output, which only occurs once, instead of repeatedly with different orderings of the items).
I try to maximize the "obviousness" of the code, rather than trying to minimize the number of calls of some specific method. For example, the above design lets a delegated call determine if a solution has been reached, rather than wrapping that check around the point of the call, which would reduce the number of calls at the expense of cluttering up the code.
Hmm, you know what's weird. The solution is seven of the first item on the menu.
Since this was obviously meant to be solved by paper and pencil in a short time frame, why not divide the order total by the price of each item to see if by some chance they ordered multiples of one item?
For example,
15.05/2.15 = 7 mixed fruits
15.05/2.75 = 5.5 french fries.
And then move on to simple combinations...
15 / (2.15 + 2.75) = 3.06122449 mixed fruits with french fries.
In other words, assume that the solution is supposed to be simple and solvable by a human without access to a computer. Then test if the simplest, most obvious (and therefore hidden in plain sight) solution(s) work(s).
I swear I'm pulling this at the local coney this weekend when I order $4.77 worth of appetizers (including tax) at 4:30 AM after the club closes.
In python.
I had some problems with "global vars" so I put the function as method of an object. It is recursive and it calls itself 29 times for the question in the comic, stopping at the first successful match
class Solver(object):
def __init__(self):
self.solved = False
self.total = 0
def solve(s, p, pl, curList = []):
poss = [i for i in sorted(pl, reverse = True) if i <= p]
if len(poss) == 0 or s.solved:
s.total += 1
return curList
if abs(poss[0]-p) < 0.00001:
s.solved = True # Solved it!!!
s.total += 1
return curList + [poss[0]]
ml,md = [], 10**8
for j in [s.solve(p-i, pl, [i]) for i in poss]:
if abs(sum(j)-p)<md: ml,md = j, abs(sum(j)-p)
s.total += 1
return ml + curList
priceList = [5.8, 4.2, 3.55, 3.35, 2.75, 2.15]
appetizers = ['Sampler Plate', 'Mozzarella Sticks', \
'Hot wings', 'Side salad', 'French Fries', 'Mixed Fruit']
menu = zip(priceList, appetizers)
sol = Solver()
q = sol.solve(15.05, priceList)
print 'Total time it runned: ', sol.total
print '-'*30
order = [(m, q.count(m[0])) for m in menu if m[0] in q]
for o in order:
print '%d x %s \t\t\t (%.2f)' % (o[1],o[0][1],o[0][0])
print '-'*30
ts = 'Total: %.2f' % sum(q)
print ' '*(30-len(ts)-1),ts
Output:
Total time it runned: 29
------------------------------
1 x Sampler Plate (5.80)
2 x Hot wings (3.55)
1 x Mixed Fruit (2.15)
------------------------------
Total: 15.05
Actually, I've refactored my algorithm some more. There were several correct combinations I was missing, and it was due to the fact that I was returning as soon as the cost went over 15.05 -- I wasn't bothering to check other (cheaper) items that I could add. Here's my new algorithm:
<cffunction name="testCombo" returntype="numeric">
<cfargument name="currentCombo" type="string" required="true" />
<cfargument name="currentTotal" type="numeric" required="true" />
<cfargument name="apps" type="array" required="true" />
<cfset var a = 0 />
<cfset var found = false />
<cfset var CC = "" />
<cfset var CT = 0 />
<cfset tries = tries + 1 />
<cfloop from="1" to="#arrayLen(arguments.apps)#" index="a">
<cfset combos = combos + 1 />
<cfset CC = listAppend(arguments.currentCombo, arguments.apps[a].name) />
<cfset CT = arguments.currentTotal + arguments.apps[a].cost />
<cfif CT eq 15.05>
<!--- print current combo --->
<cfoutput><strong>#CC# = 15.05</strong></cfoutput><br />
<cfreturn true />
<cfelseif CT gt 15.05>
<!--<cfoutput>#arguments.currentCombo# > 15.05 (aborting)</cfoutput><br />-->
<cfelse>
<!--- less than 15.50 --->
<!--<cfoutput>#arguments.currentCombo# < 15.05 (traversing)</cfoutput><br />-->
<cfset found = testCombo(CC, CT, arguments.apps) />
</cfif>
</cfloop>
<cfreturn found />
</cffunction>
<cfset mf = {name="Mixed Fruit", cost=2.15} />
<cfset ff = {name="French Fries", cost=2.75} />
<cfset ss = {name="side salad", cost=3.35} />
<cfset hw = {name="hot wings", cost=3.55} />
<cfset ms = {name="moz sticks", cost=4.20} />
<cfset sp = {name="sampler plate", cost=5.80} />
<cfset apps = [ mf, ff, ss, hw, ms, sp ] />
<cfset tries = 0 />
<cfset combos = 0 />
<cfoutput>
<cfloop from="1" to="6" index="b">
#testCombo(apps[b].name, apps[b].cost, apps)#
</cfloop>
<br />
tries: #tries#<br />
combos: #combos#
</cfoutput>
Output:
Mixed Fruit,Mixed Fruit,Mixed Fruit,Mixed Fruit,Mixed Fruit,Mixed Fruit,Mixed Fruit = 15.05
Mixed Fruit,hot wings,hot wings,sampler plate = 15.05
Mixed Fruit,hot wings,sampler plate,hot wings = 15.05
Mixed Fruit,sampler plate,hot wings,hot wings = 15.05
false false false hot wings,Mixed Fruit,hot wings,sampler plate = 15.05
hot wings,Mixed Fruit,sampler plate,hot wings = 15.05
hot wings,hot wings,Mixed Fruit,sampler plate = 15.05
hot wings,sampler plate,Mixed Fruit,hot wings = 15.05
false false sampler plate,Mixed Fruit,hot wings,hot wings = 15.05
sampler plate,hot wings,Mixed Fruit,hot wings = 15.05
false
tries: 2014
combos: 12067
I think this may have all of the correct combinations, but my question still stands: Is there a better algorithm?
Learning from #rcar's answer, and another refactoring later, I've got the following.
As with so many things I code, I've refactored from CFML to CFScript, but the code is basically the same.
I added in his suggestion of a dynamic start point in the array (instead of passing the array by value and changing its value for future recursions), which brought me to the same stats he gets (209 recursions, 571 combination price checks (loop iterations)), and then improved on that by assuming that the array will be sorted by cost -- because it is -- and breaking as soon as we go over the target price. With the break, we're down to 209 recursions and 376 loop iterations.
What other improvements could be made to the algorithm?
function testCombo(minIndex, currentCombo, currentTotal){
var a = 0;
var CC = "";
var CT = 0;
var found = false;
tries += 1;
for (a=arguments.minIndex; a <= arrayLen(apps); a++){
combos += 1;
CC = listAppend(arguments.currentCombo, apps[a].name);
CT = arguments.currentTotal + apps[a].cost;
if (CT eq 15.05){
//print current combo
WriteOutput("<strong>#CC# = 15.05</strong><br />");
return(true);
}else if (CT gt 15.05){
//since we know the array is sorted by cost (asc),
//and we've already gone over the price limit,
//we can ignore anything else in the array
break;
}else{
//less than 15.50, try adding something else
found = testCombo(a, CC, CT);
}
}
return(found);
}
mf = {name="mixed fruit", cost=2.15};
ff = {name="french fries", cost=2.75};
ss = {name="side salad", cost=3.35};
hw = {name="hot wings", cost=3.55};
ms = {name="mozarella sticks", cost=4.20};
sp = {name="sampler plate", cost=5.80};
apps = [ mf, ff, ss, hw, ms, sp ];
tries = 0;
combos = 0;
testCombo(1, "", 0);
WriteOutput("<br />tries: #tries#<br />combos: #combos#");
Here's concurrent implementation in Clojure. To calculate (items-with-price 15.05) takes about 14 combination-generation recursions, and about 10 possibility-checks. Took me about 6 minutes to calculate (items-with-price 100) on my Intel Q9300.
This only gives the first found answer, or nil if there is none, as that's all the problem calls for. Why do more work that you've been told to do ;) ?
;; np-complete.clj
;; A Clojure solution to XKCD #287 "NP-Complete"
;; By Sam Fredrickson
;;
;; The function "items-with-price" returns a sequence of items whose sum price
;; is equal to the given price, or nil.
(defstruct item :name :price)
(def *items* #{(struct item "Mixed Fruit" 2.15)
(struct item "French Fries" 2.75)
(struct item "Side Salad" 3.35)
(struct item "Hot Wings" 3.55)
(struct item "Mozzarella Sticks" 4.20)
(struct item "Sampler Plate" 5.80)})
(defn items-with-price [price]
(let [check-count (atom 0)
recur-count (atom 0)
result (atom nil)
checker (agent nil)
; gets the total price of a seq of items.
items-price (fn [items] (apply + (map #(:price %) items)))
; checks if the price of the seq of items matches the sought price.
; if so, it changes the result atom. if the result atom is already
; non-nil, nothing is done.
check-items (fn [unused items]
(swap! check-count inc)
(if (and (nil? #result)
(= (items-price items) price))
(reset! result items)))
; lazily generates a list of combinations of the given seq s.
; haven't tested well...
combinations (fn combinations [cur s]
(swap! recur-count inc)
(if (or (empty? s)
(> (items-price cur) price))
'()
(cons cur
(lazy-cat (combinations (cons (first s) cur) s)
(combinations (cons (first s) cur) (rest s))
(combinations cur (rest s))))))]
; loops through the combinations of items, checking each one in a thread
; pool until there are no more combinations or the result atom is non-nil.
(loop [items-comb (combinations '() (seq *items*))]
(if (and (nil? #result)
(not-empty items-comb))
(do (send checker check-items (first items-comb))
(recur (rest items-comb)))))
(await checker)
(println "No. of recursions:" #recur-count)
(println "No. of checks:" #check-count)
#result))
If you want an optimized algorithm, it's best to try the prices in descending order. That lets you use up as much of the remaining amount first and then see how the rest can be filled in.
Also, you can use math to figure out the maximum quantity of each food item to start each time so you don't try combinations that would go over the $15.05 goal.
This algorithm only needs to try 88 combinations to get a complete answer and that looks like the lowest that's been posted so far:
public class NPComplete {
private static final int[] FOOD = { 580, 420, 355, 335, 275, 215 };
private static int tries;
public static void main(String[] ignore) {
tries = 0;
addFood(1505, "", 0);
System.out.println("Combinations tried: " + tries);
}
private static void addFood(int goal, String result, int index) {
// If no more food to add, see if this is a solution
if (index >= FOOD.length) {
tries++;
if (goal == 0)
System.out.println(tries + " tries: " + result.substring(3));
return;
}
// Try all possible quantities of this food
// If this is the last food item, only try the max quantity
int qty = goal / FOOD[index];
do {
addFood(goal - qty * FOOD[index],
result + " + " + qty + " * " + FOOD[index], index + 1);
} while (index < FOOD.length - 1 && --qty >= 0);
}
}
Here's the output showing the two solutions:
9 tries: 1 * 580 + 0 * 420 + 2 * 355 + 0 * 335 + 0 * 275 + 1 * 215
88 tries: 0 * 580 + 0 * 420 + 0 * 355 + 0 * 335 + 0 * 275 + 7 * 215
Combinations tried: 88