Giải quyết ai là người sở hữu ngựa vằn lập trình?


128

Chỉnh sửa: câu đố này còn được gọi là "Câu đố của Einstein"

The Who sở hữu Zebra (bạn có thể thử phiên bản trực tuyến tại đây ) là một ví dụ về một bộ câu đố cổ điển và tôi cá rằng hầu hết mọi người trên Stack Overflow đều có thể giải nó bằng bút và giấy. Nhưng một giải pháp lập trình sẽ như thế nào?

Dựa trên những manh mối được liệt kê dưới đây ...

  • Có năm ngôi nhà.
  • Mỗi ngôi nhà có màu sắc độc đáo riêng.
  • Tất cả các chủ sở hữu nhà có quốc tịch khác nhau.
  • Họ đều có vật nuôi khác nhau.
  • Họ đều uống những đồ uống khác nhau.
  • Họ đều hút thuốc lá khác nhau.
  • Người đàn ông Anh sống trong ngôi nhà màu đỏ.
  • Người Thụy Điển có một con chó.
  • Người Dane uống trà.
  • Ngôi nhà xanh nằm bên trái ngôi nhà màu trắng.
  • Họ uống cà phê trong ngôi nhà xanh.
  • Người đàn ông hút Pall Mall có chim.
  • Trong ngôi nhà màu vàng họ hút thuốc Duoping.
  • Ở nhà giữa họ uống sữa.
  • Người Na Uy sống trong ngôi nhà đầu tiên.
  • Người đàn ông hút thuốc Blend sống trong ngôi nhà cạnh nhà có mèo.
  • Trong ngôi nhà bên cạnh ngôi nhà nơi họ có một con ngựa, họ hút thuốc Duoping.
  • Người đàn ông hút Blue Master uống bia.
  • Hoàng tử Đức hút thuốc.
  • Người Na Uy sống cạnh ngôi nhà màu xanh.
  • Họ uống nước trong nhà cạnh nhà nơi họ hút thuốc Blend.

... ai sở hữu ngựa vằn?


37
Ngựa vằn không bao giờ được đề cập trong danh sách thông tin (manh mối) vì vậy thông số kỹ thuật được chỉ định. Là một nhà thầu, sau đó tôi có thể tự do bỏ qua sự tồn tại của bất kỳ Zebras nào trong giải pháp, vì vậy câu trả lời của tôi chỉ đơn giản là không ai sở hữu Zebra, vì không có Zebras. : D
Peter M

10
@Peter M: Câu trả lời là 42.
Chủ yếu là vào

2
@Peter M: Vâng, thực tế là có Zebra cũng là một đầu mối, nhưng nó không được liệt kê như vậy.
activout.se

1
Âm thanh như một trường hợp sử dụng tốt cho một người giải SAT.
asmeker

Câu trả lời:


162

Đây là một giải pháp trong Python dựa trên lập trình ràng buộc:

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

Đầu ra:

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

Phải mất 0,6 giây (CPU 1,5 GHz) để tìm giải pháp.
Câu trả lời là "Người Đức sở hữu ngựa vằn".


Để cài đặt constraintmô-đun qua pip: pip cài đặt python-ràng buộc

Để cài đặt thủ công:


3
Tôi sẽ không gọi đó là không chính xác. Hạn chế duy nhất mà nó vi phạm là ngôi nhà xanh không còn lại của ngôi nhà trắng. Nhưng đó là do cách bạn xác định ràng buộc đó và có thể dễ dàng sửa chữa. Liên kết trong câu hỏi thậm chí cho phép giải pháp của bạn đưa ra định nghĩa mờ ám về "bên trái".
Mercator

4
@LFSR Tư vấn: '//' luôn là một phép chia số nguyên: '3 // 2 == 1'. '/' có thể là phân chia float '3/2 == 1.5' (trong Python 3.0 hoặc có mặt 'từ bộ phận nhập khẩu trong tương lai ') hoặc có thể là một bộ phận số nguyên (như trong C) '3/2 == 1' trên phiên bản Python cũ không có 'từ bộ phận nhập khẩu trong tương lai '.
jfs

4
Đây là chương trình ràng buộc đầu tiên tôi nhìn vào. Như nhiều người chỉ ra việc thực hiện trăn của bạn là ấn tượng. Thật là dễ thương khi bạn tránh dùng tay mã hóa các ràng buộc bằng cách sử dụng add_constraint (), và_statements và nextto_statements.
rpattabi

1
Có lý do nào để pip install python-constraintkhông? Tôi đã làm điều đó một lúc trước và nó xuất hiện để cung cấp đầu ra dự kiến.
Ben Burns

1
@BenBurns: không có lý do. Câu trả lời được viết vào năm 2008. Nếu bạn đã thử nó và nó cho kết quả tương tự thì bạn có thể cập nhật các hướng dẫn cài đặt và các liên kết tương ứng đến các tài liệu (nó không thay đổi các khía cạnh thiết yếu của câu trả lời - bạn hoàn toàn miễn phí để chỉnh sửa nó).
jfs

46

Trong Prolog, chúng ta có thể khởi tạo tên miền chỉ bằng cách chọn các thành phần từ nó :) (đưa ra các lựa chọn loại trừ lẫn nhau , cho hiệu quả). Sử dụng 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).

Chạy khá nhanh:

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

Một poster đã đề cập rằng Prolog là một giải pháp tiềm năng. Đây là sự thật, và đó là giải pháp tôi sẽ sử dụng. Nói một cách tổng quát hơn, đây là một vấn đề hoàn hảo cho một hệ thống suy luận tự động. Prolog là một ngôn ngữ lập trình logic (và trình thông dịch liên quan) tạo thành một hệ thống như vậy. Về cơ bản, nó cho phép kết luận các sự kiện từ các tuyên bố được thực hiện bằng First Order Logic . FOL về cơ bản là một hình thức logic tiên tiến hơn. Nếu bạn quyết định không muốn sử dụng Prolog, bạn có thể sử dụng một hệ thống tương tự do chính bạn tạo ra bằng cách sử dụng một kỹ thuật như modus ponens để thực hiện việc rút ra kết luận.

Tất nhiên, bạn sẽ cần thêm một số quy tắc về ngựa vằn, vì nó không được đề cập ở bất cứ đâu ... Tôi tin rằng ý định là bạn có thể tìm ra 4 thú cưng khác và do đó suy ra con cuối cùng là ngựa vằn? Bạn sẽ muốn thêm các quy tắc nói rằng ngựa vằn là một trong những thú cưng và mỗi nhà chỉ có thể có một thú cưng. Đưa loại kiến ​​thức "thông thường" này vào một hệ thống suy luận là trở ngại lớn cho việc sử dụng kỹ thuật này như một AI thực sự. Có một số dự án nghiên cứu, chẳng hạn như Cyc, đang cố gắng cung cấp kiến ​​thức phổ biến như vậy thông qua lực lượng vũ phu. Họ đã gặp một lượng thành công thú vị.


Điểm tốt về các quy tắc "lẽ thường". Tôi nhớ việc rất gắn liền với này năm trước, khi giải thích cụm từ " các nhà bên cạnh nhà" - không có ngụ ý chỉ có một? Nó không rõ ràng.
Chris

Dude cyc đã ở trong nhiều thập kỷ mà không có bất kỳ phương pháp cách mạng nào. Thật đáng buồn, sẽ thật gọn gàng khi thấy cách tiếp cận vũ phu chiến thắng các mô hình liên kết.
Josh

Chúng tôi đã sử dụng CLIPS tại uni để suy luận loại thông tin này trong khóa học AI của chúng tôi.
Josh Smeaton

15

Tương thích 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).

Tại phiên dịch:

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

13

Đây là cách tôi đi về nó. Đầu tiên tôi sẽ tạo tất cả các bộ dữ liệu được đặt hàng

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

5 ^ 6 trong số đó, 15625, dễ quản lý. Sau đó, tôi sẽ lọc ra các điều kiện boolean đơn giản. có mười người trong số họ, và mỗi người trong số họ mong muốn lọc ra 8/25 điều kiện (1/25 điều kiện có một người Thụy Điển với một con chó, 16/25 có một người không phải là người Thụy Điển với một người không phải là chó) . Tất nhiên họ không độc lập nhưng sau khi lọc những thứ đó ra thì không còn nhiều.

Sau đó, bạn đã có một vấn đề đồ thị đẹp. Tạo một biểu đồ với mỗi nút đại diện cho một trong các n-tuples còn lại. Thêm các cạnh vào biểu đồ nếu hai đầu chứa các bản sao ở một số vị trí n-tuple hoặc vi phạm bất kỳ ràng buộc 'vị trí' nào (có năm trong số đó). Từ đó bạn gần như về nhà, tìm kiếm biểu đồ cho một bộ năm nút độc lập (không có nút nào được kết nối bởi các cạnh). Nếu không có quá nhiều, bạn hoàn toàn có thể tạo ra tất cả 5 bộ dữ liệu và chỉ cần lọc lại chúng.

Đây có thể là một ứng cử viên tốt cho mã golf. Ai đó có lẽ có thể giải quyết nó trong một dòng với một cái gì đó như haskell :)

suy nghĩ lại: Bộ lọc ban đầu cũng có thể loại bỏ thông tin khỏi các ràng buộc vị trí. Không nhiều (1/25), nhưng vẫn còn đáng kể.


Đối với môn đánh gôn, về mặt kỹ thuật, một giải pháp có thể chỉ cần in ra câu trả lời, làm cho nó tương đương với môn đánh gôn "Xin chào thế giới". Bạn phải khái quát hóa vấn đề để có được một mã golf thú vị và điều này không khái quát hóa tầm thường.
Adam Rosenfield

Điểm đã lấy :) Haskell của tôi rất dài dòng nhưng dù sao điểm số của tôi vẫn ở ngoài công viên :)
Chris

1
Tôi nghĩ rằng đánh giá 5 ^ 6 của bạn về các giải pháp có thể là tắt. Tôi tin rằng số lượng kết hợp có thể có của các mục 'i' trong các danh mục 'm' sẽ là (i!) ^ (M-1). Ví dụ, năm tùy chọn cho màu sắc có thể được sắp xếp 5! cách. Cung cấp danh mục số nhà theo thứ tự, 5 loại khác cũng có thể được sắp xếp theo cách đó, có nghĩa là các kết hợp có thể là (5!) ^ 5 hoặc 24.883.200.000; cao hơn một chút so với 15.625 và thực hiện một cuộc tấn công vũ phu khó khăn hơn nhiều để giải quyết.
MidnightLightning

1
15.625 là chính xác dựa trên chiến lược giải pháp của mình. Nếu bạn muốn gán mọi trạng thái có thể cho tất cả các biến, nó sẽ lớn hơn nhiều, nhưng anh ta đang chọn chỉ xây dựng các trạng thái một phần, bỏ qua chúng, sau đó sử dụng một kỹ thuật khác để đưa ra câu trả lời cuối cùng.
Nick Larsen

9

Một giải pháp Python khác, lần này sử dụng PyKE của Python (Công cụ tri thức Python). Được cấp, nó dài dòng hơn so với việc sử dụng mô-đun "ràng buộc" của Python trong giải pháp của @JFSebastian, nhưng nó cung cấp một so sánh thú vị cho bất kỳ ai đang tìm kiếm một công cụ tri thức thô cho loại vấn đề này.

manh mối.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.

quan hệ.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 (thực sự lớn hơn, nhưng đây là bản chất)

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

Đầu ra mẫu:

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

Nguồn: https://github.com/DreadPirateShawn/pyke-who-owns-zebra


8

Đây là một đoạn trích từ giải pháp đầy đủ sử dụng NSolver , được đăng tại Einstein's Riddle trong 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
Không có nhu cầu sử dụng TinyURL ở đây, phải không? Tất cả đều trông giống như rickrolls đối với tôi.
Karl

1
Tôi đã sửa chữa tinyurl hết hạn.
jfs

@LamonteCristo Wayback máy để giải cứu.
khoảng

8

Đây là một giải pháp đơn giản trong CLP (FD) (xem thêm ):

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

Chạy nó, sản xuất:

3? - thời gian (giải (Z)).
% Suy luận 111.798, CPU 0,016 trong 0,020 giây (78% CPU, 7166493 Môi)
Z = tiếng Đức.


neighbor(X,Y) :- abs(X-Y) #= 1.
sai


7

Giải pháp ES6 (Javascript)

Với rất nhiều máy phát ES6 và một chút lodash . Bạn sẽ cần Babel để chạy này.

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

Kết quả:

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

Thời gian chạy là khoảng 2,5 giây đối với tôi, nhưng điều này có thể được cải thiện rất nhiều bằng cách thay đổi thứ tự của các quy tắc. Tôi quyết định giữ trật tự ban đầu cho rõ ràng.

Cảm ơn, đây là một thử thách thú vị!


4

Đây thực sự là một vấn đề hạn chế giải quyết. Bạn có thể làm điều đó với một kiểu lan truyền ràng buộc tổng quát trong lập trình logic như các ngôn ngữ. Chúng tôi có một bản demo dành riêng cho vấn đề Zebra trong hệ thống ALE (công cụ logic thuộc tính):

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

Đây là liên kết đến mã hóa của một câu đố Zebra đơn giản hóa:

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

Để làm điều này một cách hiệu quả là một vấn đề khác.


3

Cách dễ nhất để giải quyết các vấn đề như vậy theo lập trình là sử dụng các vòng lặp lồng nhau trên tất cả các hoán vị và kiểm tra xem kết quả có thỏa mãn các vị từ trong câu hỏi không. Nhiều vị từ có thể được nâng lên từ vòng lặp bên trong đến các vòng lặp bên ngoài để giảm đáng kể độ phức tạp tính toán cho đến khi câu trả lời có thể được tính toán trong thời gian hợp lý.

Đây là một giải pháp F # đơn giản bắt nguồn từ một bài báo trên Tạp chí F # :

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 ]

Đầu ra thu được trong 9ms là:

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

Tôi thích điều này. Tôi không ngờ rằng cuộc tấn công trực tiếp này sẽ khả thi.
phép lạ173

1

Ví dụ về Microsoft Solver Foundation từ: https://msdn.microsoft.com/en-us/l Library / ff525831% 28v = vs.93% 29.aspx? F = 255 & MSPPError =-2147217394

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

Đây là một giải pháp MiniZinc cho câu đố ngựa vằn như được định nghĩa trong 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;

Giải pháp:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.