การแก้ปัญหา "ใครเป็นเจ้าของ Zebra" โดยใช้โปรแกรม


128

แก้ไข: ปริศนานี้เรียกอีกอย่างว่า "Einstein's Riddle"

ใครเป็นเจ้าของม้าลาย (คุณสามารถลองรุ่นออนไลน์ได้ที่นี่ ) เป็นตัวอย่างของชุดคลาสสิกของปริศนาและฉันเดิมพันที่คนส่วนใหญ่ในกองมากเกินสามารถแก้ได้ด้วยปากกาและกระดาษ แต่โซลูชันแบบเป็นโปรแกรมจะมีลักษณะอย่างไร

จากเบาะแสที่ระบุด้านล่าง ...

  • มีบ้านห้าหลัง
  • บ้านแต่ละหลังมีสีที่เป็นเอกลักษณ์ของตัวเอง
  • เจ้าของบ้านทุกคนต่างเชื้อชาติ
  • พวกเขาทั้งหมดมีสัตว์เลี้ยงที่แตกต่างกัน
  • พวกเขาดื่มเครื่องดื่มที่แตกต่างกัน
  • พวกเขาสูบบุหรี่ต่างกัน
  • ชายชาวอังกฤษอาศัยอยู่ในบ้านสีแดง
  • ชาวสวีเดนมีสุนัข
  • ชาวเดนดื่มชา
  • บ้านสีเขียวอยู่ทางซ้ายของบ้านสีขาว
  • พวกเขาดื่มกาแฟในบ้านสีเขียว
  • ผู้ชายที่สูบบุหรี่พอลมอลล์มีนก
  • ในบ้านสีเหลืองพวกเขาสูบบุหรี่ Dunhill
  • ในบ้านหลังกลางพวกเขาดื่มนม
  • ชาวนอร์เวย์อาศัยอยู่ในบ้านหลังแรก
  • ชายที่สูบบุหรี่ Blend อาศัยอยู่ในบ้านข้างบ้านที่มีแมว
  • ในบ้านถัดจากบ้านที่มีม้าพวกเขาสูบบุหรี่ Dunhill
  • ผู้ชายที่สูบบุหรี่ Blue Master ดื่มเบียร์
  • ชาวเยอรมันสูบบุหรี่ให้เจ้าชาย
  • ชาวนอร์เวย์อาศัยอยู่ติดกับบ้านสีฟ้า
  • พวกเขาดื่มน้ำในบ้านข้างๆบ้านที่สูบเบลนด์

... ใครเป็นเจ้าของ Zebra?


37
ไม่เคยมีการกล่าวถึงม้าลายในรายการข้อมูล (เบาะแส) ดังนั้นข้อกำหนดจึงอยู่ภายใต้การระบุ ในฐานะผู้รับเหมาฉันมีอิสระที่จะเพิกเฉยต่อการมีอยู่ของม้าลายใด ๆ ในการแก้ปัญหาดังนั้นคำตอบของฉันก็คือไม่มีใครเป็นเจ้าของม้าลายเพราะไม่มีม้าลาย : D
Peter M

10
@Peter M: คำตอบคือ 42
อันตราย

2
@Peter M: ใช่ความจริงที่ว่ามีม้าลายก็เป็นเบาะแสเช่นกัน แต่มันไม่ได้อยู่ในรายการเช่นนี้
activout.se

1
ดูเหมือนจะเป็นกรณีการใช้งานที่ดีสำหรับตัวแก้ SAT
asmeurer

คำตอบ:


162

นี่คือวิธีแก้ปัญหาใน Python ตามการเขียนโปรแกรมข้อ จำกัด :

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()

เอาท์พุท:

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

ใช้เวลา 0.6 วินาที (CPU 1.5GHz) ในการค้นหาโซลูชัน
คำตอบคือ "เยอรมันเป็นเจ้าของม้าลาย"


ในการติดตั้งconstraintโมดูลผ่านpip: pip install python-constraint

ในการติดตั้งด้วยตนเอง:


3
ฉันจะไม่เรียกว่าไม่ถูกต้อง ข้อ จำกัด เดียวที่ละเมิดคือบ้านสีเขียวไม่เหลืออยู่ในบ้านสีขาว แต่นั่นเป็นเพราะวิธีที่คุณกำหนดข้อ จำกัด นั้นและสามารถแก้ไขได้อย่างง่ายดาย ลิงก์ในคำถามยังช่วยให้การแก้ปัญหาของคุณมีคำจำกัดความที่มืดมนของ "left of"
mercator

4
ที่ปรึกษา @LFSR: '//' คือการหารจำนวนเต็มเสมอ: '3 // 2 == 1' '/' อาจเป็นส่วนลอย '3/2 == 1.5' (ใน Python 3.0 หรือต่อหน้า 'จากการแบ่งการนำเข้าในอนาคต ') หรืออาจเป็นการหารจำนวนเต็ม (เช่นใน C) '3/2 == 1' บน Python เวอร์ชันเก่าที่ไม่มี "จากแผนกนำเข้าในอนาคต "
jfs

4
นี่เป็นโปรแกรมข้อ จำกัด แรกที่ฉันดู หลายคนชี้ให้เห็นว่าการใช้งาน python ของคุณนั้นน่าประทับใจ มันน่ารักมากที่คุณหลีกเลี่ยงการเขียนโค้ดข้อ จำกัด ด้วยมือด้วยการใช้ add_constraints (), and_statements และ nextto_statements
rpattabi

1
มีเหตุผลpip install python-constraintอะไรที่จะไม่ทำ? ฉันทำไปเมื่อสักครู่ที่ผ่านมาและดูเหมือนว่าจะให้ผลลัพธ์ที่คาดหวัง
Ben Burns

1
@ BenBurns: ไม่มีเหตุผล คำตอบถูกเขียนขึ้นในปี 2008 หากคุณได้ทดสอบและให้ผลลัพธ์เดียวกันคุณสามารถอัปเดตคำแนะนำในการติดตั้งและลิงก์ที่เกี่ยวข้องไปยังเอกสารได้ (จะไม่เปลี่ยนแปลงลักษณะสำคัญของคำตอบ - คุณเป็นอิสระ เพื่อแก้ไข)
jfs

46

ใน Prolog เราสามารถสร้างอินสแตนซ์ของโดเมนได้เพียงแค่เลือกองค์ประกอบจากมัน :) (ทำการเลือกเฉพาะร่วมกันเพื่อประสิทธิภาพ) ใช้ 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).

ทำงานได้ทันที:

?- 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.

16

ผู้โพสต์คนหนึ่งกล่าวไว้แล้วว่า Prolog เป็นโซลูชันที่มีศักยภาพ นี่เป็นความจริงและเป็นวิธีแก้ปัญหาที่ฉันจะใช้ โดยทั่วไปแล้วนี่เป็นปัญหาที่สมบูรณ์แบบสำหรับระบบการอนุมานอัตโนมัติ Prolog เป็นภาษาโปรแกรมลอจิก (และตัวแปลที่เกี่ยวข้อง) ที่สร้างระบบดังกล่าว มันเป็นพื้นช่วยให้การสรุปข้อเท็จจริงจากงบทำโดยใช้ลอจิกการสั่งซื้อครั้งแรก โดยพื้นฐานแล้ว FOL เป็นรูปแบบของตรรกะเชิงโจทย์ขั้นสูง หากคุณตัดสินใจว่าคุณไม่ต้องการใช้ Prolog คุณสามารถใช้ระบบที่คล้ายกันในการสร้างของคุณเองโดยใช้เทคนิคเช่นmodus ponensเพื่อดำเนินการหาข้อสรุป

แน่นอนคุณจะต้องเพิ่มกฎบางอย่างเกี่ยวกับม้าลายเนื่องจากมันไม่ได้กล่าวถึงที่ใด ... ฉันเชื่อว่าเจตนาคือคุณสามารถหาสัตว์เลี้ยงอีก 4 ตัวได้และสรุปได้ว่าสุดท้ายคือม้าลาย? คุณจะต้องเพิ่มกฎที่ระบุว่าม้าลายเป็นหนึ่งในสัตว์เลี้ยงและแต่ละบ้านสามารถมีสัตว์เลี้ยงได้เพียงตัวเดียว การนำความรู้ "สามัญสำนึก" ประเภทนี้มาใช้ในระบบการอนุมานเป็นอุปสรรคสำคัญในการใช้เทคนิคนี้ให้เป็น AI ที่แท้จริง มีโครงการวิจัยบางอย่างเช่น Cyc ซึ่งพยายามให้ความรู้ทั่วไปดังกล่าวผ่านพลังเดรัจฉาน พวกเขาได้พบกับความสำเร็จที่น่าสนใจ


จุดดีเกี่ยวกับกฎ "สามัญสำนึก" ผมจำได้ว่าได้รับการเชื่อมโยงมากขึ้นกับปีนี้ที่ผ่านมาเมื่อการตีความคำว่า " บ้านติดกับบ้าน" - ไม่ว่าบ่งบอกถึงการมีเพียงหนึ่ง? มันไม่ชัดเจน
คริส

Dude Cycle อยู่ในการพัฒนามานานหลายทศวรรษโดยไม่มีวิธีการปฏิวัติใด ๆ น่าเศร้าคงจะเป็นเรื่องที่ดีที่ได้เห็นวิธีการบังคับแบบเดรัจฉานที่ได้รับชัยชนะเหนือโมเดลที่เชื่อมโยง
Josh

เราใช้ CLIPS ที่ uni เพื่อสรุปข้อมูลประเภทนี้ในหลักสูตร AI ของเรา
Josh Smeaton

15

รองรับ SWI-Prolog:

% 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).

ที่ล่าม:

?- get_zebra(Street, Who).
Street = ...
Who = german

13

นี่คือวิธีที่ฉันจะทำ ก่อนอื่นฉันจะสร้าง n-tuples ที่สั่งซื้อทั้งหมด

(housenumber, color, nationality, pet, drink, smoke)

5 ^ 6 จากทั้งหมด 15625 จัดการได้ง่าย จากนั้นฉันจะกรองเงื่อนไขบูลีนง่ายๆ มีทั้งหมดสิบตัวและแต่ละเงื่อนไขที่คุณคาดว่าจะกรองออก 8/25 ของเงื่อนไข (1/25 ของเงื่อนไขมีชาวสวีเดนกับสุนัข 16/25 มีสัตว์ที่ไม่ใช่ชาวสวีเดนและสุนัขที่ไม่ใช่สุนัข) . แน่นอนว่าพวกเขาไม่ได้เป็นอิสระ แต่หลังจากกรองสิ่งเหล่านั้นออกไปแล้วก็ไม่ควรมีเหลือ

หลังจากนั้นคุณก็มีปัญหาเรื่องกราฟที่ดี สร้างกราฟโดยแต่ละโหนดแทนหนึ่งใน n-tuples ที่เหลือ เพิ่มขอบให้กับกราฟหากปลายทั้งสองมีการซ้ำกันในตำแหน่ง n-tuple บางตำแหน่งหรือละเมิดข้อ จำกัด 'ตำแหน่ง' ใด ๆ (มีห้าข้อ) จากนั้นคุณก็เกือบจะถึงบ้านแล้วให้ค้นหากราฟเพื่อหาชุดโหนดห้าโหนดที่เป็นอิสระ (โดยไม่มีโหนดใดเชื่อมต่อด้วยขอบ) หากมีไม่มากเกินไปคุณอาจสร้าง n-tuples ทั้ง 5 ตัวทั้งหมดอย่างละเอียดแล้วกรองอีกครั้ง

นี่อาจเป็นตัวเลือกที่ดีสำหรับโค้ดกอล์ฟ อาจมีใครบางคนสามารถแก้ได้ในบรรทัดเดียวด้วยบางอย่างเช่น haskell :)

ภายหลัง:การส่งผ่านตัวกรองเริ่มต้นยังสามารถกำจัดข้อมูลจากข้อ จำกัด ด้านตำแหน่ง ไม่มาก (1/25) แต่ก็ยังมีนัยสำคัญ


สำหรับโค้ดกอล์ฟวิธีแก้ปัญหาสามารถพิมพ์คำตอบออกมาในทางเทคนิคทำให้เทียบเท่ากับโค้ดกอล์ฟ "Hello world" คุณต้องสรุปปัญหาเพื่อให้ได้โค้ดกอล์ฟที่น่าสนใจและสิ่งนี้ไม่ได้เป็นการสรุปเล็กน้อย
Adam Rosenfield

จุดที่ได้รับ :) haskell ของฉันเป็นแบบ verbose แต่คะแนนของฉันก็ไม่ได้อยู่ที่สวนสาธารณะ :)
Chris

1
ฉันคิดว่าการประเมินโซลูชันที่เป็นไปได้ 5 ^ 6 ของคุณปิดอยู่ ฉันเชื่อว่าจำนวนชุดค่าผสมที่เป็นไปได้ของรายการ "i" ในหมวดหมู่ "m" ควรเป็น (i!) ^ (m-1) ตัวอย่างเช่นห้าตัวเลือกสำหรับสีสามารถจัดเรียงได้ 5! วิธี การจัดให้หมวดหมู่เลขที่บ้านอยู่ในลำดับเดียวกันอีก 5 หมวดหมู่แต่ละหมวดสามารถจัดเรียงได้เช่นกันซึ่งหมายความว่าชุดค่าผสมที่เป็นไปได้คือ (5!) ^ 5 หรือ 24,883,200,000; ค่อนข้างสูงกว่า 15,625 เล็กน้อยและทำให้การโจมตีที่ดุร้ายยากขึ้นมากในการรับมือ
MidnightLightning

1
15,625 ถูกต้องตามกลยุทธ์การแก้ปัญหาของเขา หากคุณต้องการกำหนดทุกสถานะที่เป็นไปได้สำหรับตัวแปรทั้งหมดมันจะมีขนาดใหญ่กว่ามาก แต่เขาเลือกที่จะสร้างสถานะเพียงบางส่วนแยกไปที่สถานะเหล่านั้นจากนั้นใช้เทคนิคอื่นเพื่อรวบรวมคำตอบสุดท้าย
Nick Larsen

9

อีกวิธีแก้ปัญหา Python คราวนี้ใช้ Python ของ Python (Python Knowledge Engine) จริงอยู่ที่มันละเอียดกว่าการใช้โมดูล "ข้อ จำกัด " ของ Python ในโซลูชันโดย @JFSebastian แต่เป็นการเปรียบเทียบที่น่าสนใจสำหรับใครก็ตามที่กำลังมองหาเครื่องมือความรู้ดิบสำหรับปัญหาประเภทนี้

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 (ใหญ่กว่าจริง แต่นี่คือสาระสำคัญ)

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

ตัวอย่างผลลัพธ์:

$ 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.

ที่มา: https://github.com/DreadPirateShawn/pyke-who-owns-zebra


8

นี่คือข้อความที่ตัดตอนมาจากโซลูชันเต็มรูปแบบโดยใช้NSolverโพสต์ที่Einstein's Riddle ใน 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));

5
ไม่จำเป็นต้องใช้ TinyURL ที่นี่มีไหม? พวกเขาทั้งหมดดูเหมือนริกโรลสำหรับฉัน
Karl

1
ฉันได้แก้ไข Tinyurl ที่หมดอายุแล้ว
jfs

@LamonteCristo เครื่อง Waybackเพื่อช่วยเหลือ
ประมาณ

8

นี่คือวิธีแก้ปัญหาที่ตรงไปตรงมาใน CLP (FD) (ดูเพิ่มเติม ):

:- 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)).

ใช้มันผลิต:

3? - เวลา (แก้ (Z))
การอนุมาน% 111,798, 0.016 CPU ใน 0.020 วินาที (CPU 78%, 7166493 Lips)
Z = เยอรมัน


neighbor(X,Y) :- abs(X-Y) #= 1.
เท็จ


7

โซลูชัน ES6 (Javascript)

ด้วยเครื่องกำเนิดไฟฟ้า ES6จำนวนมากและlodashเล็กน้อย คุณจะต้องBabelเพื่อเรียกใช้สิ่งนี้

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);
}

ผลลัพธ์:

[ { 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' } ]

เวลาทำงานประมาณ 2.5 วินาทีสำหรับฉัน แต่สิ่งนี้สามารถปรับปรุงได้มากโดยการเปลี่ยนลำดับของกฎ ฉันตัดสินใจที่จะคงคำสั่งเดิมไว้เพื่อความชัดเจน

ขอบคุณนี่เป็นความท้าทายที่ยอดเยี่ยม!


4

นี่คือข้อ จำกัด ในการแก้ปัญหา คุณสามารถทำได้ด้วยการเผยแพร่ข้อ จำกัด ทั่วไปในการเขียนโปรแกรมเชิงตรรกะเช่นภาษา เรามีการสาธิตเฉพาะสำหรับปัญหา Zebra ในระบบ ALE (attribute logic engine):

http://www.cs.toronto.edu/~gpenn/ale.html

นี่คือลิงค์ไปยังการเข้ารหัสของตัวต่อ Zebra แบบง่าย:

http://www.cs.toronto.edu/~gpenn/ale/files/grammars/baby.pl

การทำเช่นนี้อย่างมีประสิทธิภาพเป็นอีกเรื่องหนึ่ง


3

วิธีที่ง่ายที่สุดในการแก้ปัญหาดังกล่าวโดยใช้โปรแกรมคือการใช้การวนซ้ำแบบซ้อนทับการเรียงสับเปลี่ยนทั้งหมดและตรวจสอบเพื่อดูว่าผลลัพธ์เป็นไปตามเพรดิเคตในคำถามหรือไม่ สามารถยกเพรดิเคตจำนวนมากจากวงในไปยังลูปด้านนอกเพื่อลดความซับซ้อนในการคำนวณลงอย่างมากจนกว่าจะคำนวณคำตอบได้ในเวลาที่เหมาะสม

นี่คือวิธีแก้ปัญหา F # ง่ายๆที่ได้มาจากบทความใน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 ]

ผลลัพธ์ที่ได้ใน 9ms คือ:

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"])]

ฉันชอบสิ่งนี้. ฉันไม่ได้คาดหวังว่าการโจมตีโดยตรงนี้จะเป็นไปได้
miracle173

1

ตัวอย่าง Microsoft Solver Foundation จาก: 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.");
}

1

นี่คือโซลูชัน MiniZinc สำหรับปริศนาม้าลายตามที่กำหนดไว้ใน 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;

สารละลาย:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.