Zebra / backend /Example.txt
guoj5's picture
Optimised the demonstration, Added constraint judge.
b9c9b71
This is an example of representing a Zebra Puzzle using a Z3-based Python code:
There are 5 Producers in this puzzle.
Categories and possible items:
- Shirt: black, green, orange, pink, red
- Name: Barbara, Isabella, Jane, Nicole, Rachel
- Jam: apricot, cherry, fig, raspberry, watermelon
- Size: 10 oz, 12 oz, 14 oz, 20 oz, 6 oz
- Source: backyard, farmers' coop, local grocer, organic farm, wild pickings
Clues:
- The producer whose jam comes from Wild pickings is in the fourth position.
- The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
- Isabella is selling Cherry jam.
- The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
- The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
- The producer with 14 oz jars is next to the one selling Raspberry jam.
- The Raspberry jam producer is positioned at one of the ends.
- Barbara is next to the producer whose jam source is the Organic farm.
- Jane is located somewhere between Nicole and Isabella, in that order.
- The producer of Fig jam sources the fruit from an Organic farm.
- The producer with 10 oz jars is at one of the ends.
- The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
- The producer with 12 oz jars is next to the one who has 6 oz jars.
- Isabella is next to the producer wearing a Black shirt.
- The producer with 6 oz jars is next to the one whose source is the Local grocer.
- The producer with 6 oz jars is in the second position.
- Rachel's source of fruit is the Farmers' coop.
- Barbara is next to Nicole.
- The producer wearing an Orange shirt gets her fruit from the Backyard.
- The producer with 12 oz jars is in the very first position.
```python
from z3 import *
import json
import sys
# Define all categories in a single list of tuples:
# (House is implicit; each row's first column will be the house number.)
categories = [
("Shirt", ["black", "green", "orange", "pink", "red"]),
("Name", ["Barbara", "Isabella", "Jane", "Nicole", "Rachel"]),
("Jam", ["apricot", "cherry", "fig", "raspberry", "watermelon"]),
("Size", ["10 oz", "12 oz", "14 oz", "20 oz", "6 oz"]),
("Source", ["backyard", "farmers' coop", "local grocer", "organic farm", "wild pickings"])
]
# No need to change here, automatically processing
NUM_POSITIONS = len(categories[0][1])
item_to_cat_and_index = {}
for cat_idx, (cat_str, item_list) in enumerate(categories):
for item_idx, item_str in enumerate(item_list):
item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
Vars = []
for cat_idx, (cat_name, item_list) in enumerate(categories):
var = IntVector(cat_name, len(item_list))
Vars.append(var)
s = Solver()
for cat_idx, (cat_name, item_list) in enumerate(categories):
for item_idx, item_str in enumerate(item_list):
s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
s.add(Distinct(Vars[cat_idx]))
def pos(cat_str, item_str):
(cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
return Vars[cat_idx][item_idx]
# All clues here
# The producer whose jam comes from Wild pickings is in the fourth position.
s.add(pos("Source", "wild pickings") == 4)
# The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
s.add(pos("Jam", "cherry") > pos("Shirt", "green"))
# Isabella is selling Cherry jam.
s.add(pos("Name", "Isabella") == pos("Jam", "cherry"))
# The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
s.add(And(pos("Source", "backyard") < pos("Shirt", "pink"), pos("Shirt", "pink") < pos("Jam", "watermelon")))
# The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
s.add(pos("Size", "14 oz") > pos("Shirt", "green"))
# The producer with 14 oz jars is next to the one selling Raspberry jam.
s.add(Abs(pos("Size", "14 oz") - pos("Jam", "raspberry")) == 1)
# The Raspberry jam producer is positioned at one of the ends.
s.add(Or(pos("Jam", "raspberry") == 1, pos("Jam", "raspberry") == NUM_POSITIONS))
# Barbara is next to the producer whose jam source is the Organic farm.
s.add(Abs(pos("Name", "Barbara") - pos("Source", "organic farm")) == 1)
# Jane is located somewhere between Nicole and Isabella, in that order.
s.add(And(pos("Name", "Nicole") < pos("Name", "Jane"), pos("Name", "Jane") < pos("Name", "Isabella")))
# The producer of Fig jam sources the fruit from an Organic farm.
s.add(pos("Jam", "fig") == pos("Source", "organic farm"))
# The producer with 10 oz jars is at one of the ends.
s.add(Or(pos("Size", "10 oz") == 1, pos("Size", "10 oz") == NUM_POSITIONS))
# The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
s.add(pos("Jam", "raspberry") > pos("Shirt", "green"))
# The producer with 12 oz jars is next to the one who has 6 oz jars.
s.add(Abs(pos("Size", "12 oz") - pos("Size", "6 oz")) == 1)
# Isabella is next to the producer wearing a Black shirt.
s.add(Abs(pos("Name", "Isabella") - pos("Shirt", "black")) == 1)
# The producer with 6 oz jars is next to the one whose source is the Local grocer.
s.add(Abs(pos("Size", "6 oz") - pos("Source", "local grocer")) == 1)
# The producer with 6 oz jars is in the second position.
s.add(pos("Size", "6 oz") == 2)
# Rachel's source of fruit is the Farmers' coop.
s.add(pos("Name", "Rachel") == pos("Source", "farmers' coop"))
# Barbara is next to Nicole.
s.add(Abs(pos("Name", "Barbara") - pos("Name", "Nicole")) == 1)
# The producer wearing an Orange shirt gets her fruit from the Backyard.
s.add(pos("Shirt", "orange") == pos("Source", "backyard"))
# The producer with 12 oz jars is in the very first position.
s.add(pos("Size", "12 oz") == 1)
# Solve the puzzle
if s.check() == sat:
m = s.model()
rows = []
header = ["House"] + [cat_name for cat_name, _ in categories]
for position in range(1, NUM_POSITIONS + 1):
row = [str(position)]
for cat_idx, (cat_name, item_list) in enumerate(categories):
for item_idx, item_str in enumerate(item_list):
if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
row.append(item_str)
break
rows.append(row)
result_dict = {"header": header, "rows": rows}
cnt = 1
block = []
for cat_idx, (cat_name, item_list) in enumerate(categories):
for i in range(NUM_POSITIONS):
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
s.add(Or(block))
while s.check() == sat:
m = s.model()
cnt += 1
block = []
for cat_idx, (cat_name, item_list) in enumerate(categories):
for i in range(NUM_POSITIONS):
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
s.add(Or(block))
if cnt == 1:
# Output the solution as a JSON string.
print(json.dumps(result_dict))
else:
print(json.dumps({"error": "Multiple solutions found."}))
else:
print(json.dumps({"error": "No solution found."}))
sys.stdout.flush() # Force immediate output
```
Based on this example, write a similar Z3-based Python code by changing only categories and constraints to represent the puzzle given below.