如何用 z3py 以优雅的方式解决爱因斯坦之谜?

How to solve Einsteins riddle with z3py the elegant way?

z3py 伙计们提供了基于此处的代码 https://github.com/0vercl0k/z3-playground/blob/master/einstein_riddle_z3.py . However comparing to this https://artificialcognition.github.io/who-owns-the-zebra 解决方案相当复杂、冗长且丑陋。我真的不想切换库,因为 z3py 看起来更高级和维护。所以我开始研究我的版本,但我没有声明某些部分(缺乏知识或不可能?)。这是我所拥有的以及我被卡住的地方(2 条评论):

from z3 import *

color = Int('color')
nationality = Int('nationality')
beverage = Int('beverage')
cigar = Int('cigar')
pet = Int('pet')
house = Int('house')

color_variations = Or(color==1, color==2, color==3, color==4, color==5)
nationality_variations = Or(nationality==1, nationality==2, nationality==3, nationality==4, nationality==5)
beverage_variations = Or(beverage==1, beverage==2, beverage==3, beverage==4, beverage==5)
cigar_variations = Or(cigar==1, cigar==2, cigar==3, cigar==4, cigar==5)
pet_variations = Or(pet==1, pet==2, pet==3, pet==4, pet==5)
house_variations = Or(house==1, house==2, house==3, house==4, house==5)

s = Solver()

s.add(color_variations)
s.add(nationality_variations)
s.add(beverage_variations)
s.add(cigar_variations)
s.add(pet_variations)
s.add(house_variations)

# This is not right
#s.add(Distinct([color, nationality, beverage, cigar, pet]))

s.add(And(Implies(nationality==1, color==1), Implies(color==1, nationality==1))) #the Brit (nationality==1) lives in the red (color==1) house
s.add(And(Implies(nationality==2, pet==1), Implies(pet==1, nationality==2))) #the Swede (nationality==2) keeps dogs (pet==1) as pets
s.add(And(Implies(nationality==3, beverage==1), Implies(beverage==1, nationality==3))) #the Dane (nationality==3) drinks tea (beverage=1)
s.add(And(Implies(color==2, beverage==2), Implies(beverage==2, color==2))) #the green (color==2) house's owner drinks coffee (beverage==2)
s.add(And(Implies(cigar==1, pet==2), Implies(pet==2, cigar==1))) #the person who smokes Pall Mall (cigar==1) rears birds ([pet==2])
s.add(And(Implies(color==4, cigar==2), Implies(cigar==2, color==4))) #the owner of the yellow (color==4) house smokes Dunhill (cigar==2)
s.add(And(Implies(house==3, beverage==3), Implies(beverage==3, house==3))) #the man living in the center (hause==3) house drinks milk (beverage==3)
s.add(And(Implies(nationality==4, house==1), Implies(house==1, nationality==4))) #the Norwegian (nationality==4) lives in the first house (house==1)
s.add(And(Implies(cigar==3, beverage==4), Implies(beverage==4, cigar==3))) #the owner who smokes BlueMaster (cigar==3) drinks beer (beverage==4)
s.add(And(Implies(nationality==5, cigar==4), Implies(cigar==4, nationality==5))) #the German (nationality==5) smokes Prince (cigar==4)

# I can't figure our this part, so I can keep it short and efficient
# the green (color==2) house is on the left of the white (color==3) house

目前正在研究 ForAllFunctions

的方向

您应该对此处的不同种类的事物使用枚举。此外,您不能仅仅拥有一个颜色变量就可以逃脱:毕竟,每个房子都有不同的颜色,并且您想要单独跟踪它。一个更好的想法是让 colornationality 等都是未解释的函数;分别将数字映射到颜色、国家等。

这是此问题的 Haskell 解决方案,使用通过 SMTLib 接口使用 z3 的 SBV 库,遵循我描述的策略:https://hackage.haskell.org/package/sbv-8.8/docs/src/Documentation.SBV.Examples.Puzzles.Fish.html

将此策略转化为 Python,我们有:

from z3 import *

# Sorts of things we have
Color      , (Red     , Green   , White     , Yellow   , Blue)   = EnumSort('Color'      , ('Red'     , 'Green'   , 'White'     , 'Yellow'   , 'Blue'))
Nationality, (Briton  , Dane    , Swede     , Norwegian, German) = EnumSort('Nationality', ('Briton'  , 'Dane'    , 'Swede'     , 'Norwegian', 'German'))
Beverage   , (Tea     , Coffee  , Milk      , Beer     , Water)  = EnumSort('Beverage'   , ('Tea'     , 'Coffee'  , 'Milk'      , 'Beer'     , 'Water'))
Pet        , (Dog     , Horse   , Cat       , Bird     , Fish)   = EnumSort('Pet'        , ('Dog'     , 'Horse'   , 'Cat'       , 'Bird'     , 'Fish'))
Sport      , (Football, Baseball, Volleyball, Hockey   , Tennis) = EnumSort('Sport'      , ('Football', 'Baseball', 'Volleyball', 'Hockey'   , 'Tennis'))

# Uninterpreted functions to match "houses" to these sorts. We represent houses by regular symbolic integers.
c = Function('color',       IntSort(), Color)
n = Function('nationality', IntSort(), Nationality)
b = Function('beverage',    IntSort(), Beverage)
p = Function('pet',         IntSort(), Pet)
s = Function('sport',       IntSort(), Sport)

S = Solver()

# Create a new fresh variable. We don't care about its name
v = 0
def newVar():
    global v
    i = Int("v" + str(v))
    v = v + 1
    S.add(1 <= i, i <= 5)
    return i

# Assert a new fact. This is just a synonym for add, but keeps everything uniform
def fact0(f):
    S.add(f)

# Assert a fact about a new fresh variable
def fact1(f):
    i = newVar()
    S.add(f(i))

# Assert a fact about two fresh variables
def fact2(f):
    i = newVar()
    j = newVar()
    S.add(i != j)
    S.add(f(i, j))

# Assert two houses are next to each other
def neighbor(i, j):
    return (Or(i == j+1, j == i+1))

fact1 (lambda i   : And(n(i) == Briton,     c(i) == Red))                       # The Briton lives in the red house.
fact1 (lambda i   : And(n(i) == Swede,      p(i) == Dog))                       # The Swede keeps dogs as pets.
fact1 (lambda i   : And(n(i) == Dane,       b(i) == Tea))                       # The Dane drinks tea.
fact2 (lambda i, j: And(c(i) == Green,      c(j) == White, i == j-1))           # The green house is left to the white house.
fact1 (lambda i   : And(c(i) == Green,      b(i) == Coffee))                    # The owner of the green house drinks coffee.
fact1 (lambda i   : And(s(i) == Football,   p(i) == Bird))                      # The person who plays football rears birds.
fact1 (lambda i   : And(c(i) == Yellow,     s(i) == Baseball))                  # The owner of the yellow house plays baseball.
fact0 (                 b(3) == Milk)                                           # The man living in the center house drinks milk.
fact0 (                 n(1) == Norwegian)                                      # The Norwegian lives in the first house.
fact2 (lambda i, j: And(s(i) == Volleyball, p(j) == Cat,      neighbor(i, j)))  # The man who plays volleyball lives next to the one who keeps cats.
fact2 (lambda i, j: And(p(i) == Horse,      s(j) == Baseball, neighbor(i, j)))  # The man who keeps the horse lives next to the one who plays baseball.
fact1 (lambda i   : And(s(i) == Tennis,     b(i) == Beer))                      # The owner who plays tennis drinks beer.
fact1 (lambda i   : And(n(i) == German,     s(i) == Hockey))                    # The German plays hockey.
fact2 (lambda i, j: And(n(i) == Norwegian,  c(j) == Blue,     neighbor(i, j)))  # The Norwegian lives next to the blue house.
fact2 (lambda i, j: And(s(i) == Volleyball, b(j) == Water,    neighbor(i, j)))  # The man who plays volleyball has a neighbor who drinks water.

# Determine who owns the fish
fishOwner = Const("fishOwner", Nationality)
fact1 (lambda i: And(n(i) == fishOwner, p(i) == Fish))

r = S.check()
if r == sat:
    m = S.model()
    print(m[fishOwner])
else:
    print("Solver said: %s" % r)

当我 运行 这个时,我得到:

$ python a.py
German

显示 fish-owner 是德语。我认为你的原始问题有一组不同但相似的约束,你可以很容易地使用相同的策略来解决你的原始问题。

查看以下输出也很有指导意义:

print(m)

sat 的情况下。这打印:

[v5 = 4,
 v9 = 1,
 v16 = 2,
 v12 = 5,
 v14 = 1,
 v2 = 2,
 v0 = 3,
 v10 = 2,
 v18 = 4,
 v15 = 2,
 v6 = 3,
 v7 = 1,
 v4 = 5,
 v8 = 2,
 v17 = 1,
 v11 = 1,
 v1 = 5,
 v13 = 4,
 fishOwner = German,
 v3 = 4,
 nationality = [5 -> Swede,
                2 -> Dane,
                1 -> Norwegian,
                4 -> German,
                else -> Briton],
 color = [5 -> White,
          4 -> Green,
          1 -> Yellow,
          2 -> Blue,
          else -> Red],
 pet = [3 -> Bird,
        1 -> Cat,
        2 -> Horse,
        4 -> Fish,
        else -> Dog],
 beverage = [4 -> Coffee,
             3 -> Milk,
             5 -> Beer,
             1 -> Water,
             else -> Tea],
 sport = [1 -> Baseball,
          2 -> Volleyball,
          5 -> Tennis,
          4 -> Hockey,
          else -> Football]]

忽略对 vN 变量的所有赋值,这些是我们在内部用于建模目的的变量。但是您可以看到 z3 如何将每个未解释的函数映射到相应的值。对于所有这些,映射的值是房子的数量到满足拼图约束的相应值。您可以使用此模型中包含的信息,根据需要以编程方式提取拼图的完整解决方案。