프로그래밍 방식으로 "누가 Zebra를 소유하고 있습니까?"


128

편집 :이 퍼즐은 "아인슈타인의 수수께끼"라고도합니다.

얼룩말을 소유 한 사람 (당신은 할 수 있습니다 여기에 온라인 버전을 사용해이 ) 퍼즐의 고전적인 설정의 예를 들어 내가 스택 오버플로 대부분의 사람들이 종이와 펜을 해결할 수 있습니다 내기. 그러나 프로그래밍 솔루션은 어떻게 생겼습니까?

아래 나열된 단서를 기반으로 ...

  • 다섯 집이 있습니다.
  • 각 집마다 고유 한 색상이 있습니다.
  • 모든 주택 소유자는 국적이 다릅니다.
  • 그들은 모두 다른 애완 동물이 있습니다.
  • 그들은 모두 다른 음료를 마신다.
  • 그들은 모두 다른 담배를 피 웁니다.
  • 영국인은 빨간 집에 산다.
  • 스웨덴에는 개가 있습니다.
  • 데인은 차를 마신다.
  • 그린 하우스는 백악관의 왼쪽에 있습니다.
  • 그들은 온실에서 커피를 마신다.
  • Pall Mall을 피우는 사람은 새가 있습니다.
  • 노란 집에서 그들은 던힐을 피 웁니다.
  • 중간 집에서 그들은 우유를 마신다.
  • 노르웨이 인은 첫 번째 집에 산다.
  • 담배를 피우는 사람은 고양이와 함께 집 옆집에 산다.
  • 말이있는 집 옆 집에서 던힐을 피 웁니다.
  • 블루 마스터를 피우는 사람은 맥주를 마신다.
  • 독일은 왕자를 연기가 난다.
  • 노르웨이는 청와대 옆에 산다.
  • 그들은 블렌드를 피우는 집 옆 집에서 물을 마신다.

... 얼룩말을 누가 소유합니까?


37
정보 (단서) 목록에는 얼룩말이 언급되지 않았으므로 사양이 지정되어 있습니다. 계약자로서 솔루션에 Zebra가 존재하는 것을 자유롭게 무시할 수 있습니다. 따라서 Zebra가 없기 때문에 아무도 Zebra를 소유하고 있지 않다는 것입니다. : D
Peter M

10
@ 피터 M : 대답은 42였습니다.
무해한

2
@Peter M : 그렇습니다. Zebra가 있다는 사실도 실마리이지만, 그 목록에는 없습니다.
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)가 걸립니다.
대답은 "독일인은 얼룩말을 소유하고 있습니다."


pip install python-constraint 를 통해 constraint모듈 을 설치하려면pip

수동으로 설치하려면


3
나는 그것을 부정확하게 부르지 않을 것입니다. 그것이 위반하는 유일한 제약은 온실이 백악관에서 떠나지 않는다는 것입니다. 그러나 이는 구속 조건을 정의한 방식으로 인해 쉽게 고칠 수 있습니다. 질문의 링크는 심지어 "왼쪽"의 어두운 정의를 고려한 솔루션을 허용합니다.
mercator

4
@LFSR 컨설팅 : '//'는 항상 정수 나누기입니다 : '3 // 2 == 1'. '/'는 float 나누기 '3/2 == 1.5'(Python 3.0에서 또는 ' 미래 수입 가져 오기 '에서 존재 ) 일 수 있거나 정수 나누기 (C에서와 같이) '3/2 == 1'일 수 있습니다. ' 미래의 수입 부서에서' 없는 오래된 파이썬 버전 .
jfs

4
이것은 내가 본 첫 번째 제약 프로그램입니다. 많은 사람들이 지적한 것처럼 파이썬 구현은 인상적입니다. 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가 잠재적 솔루션이라고 언급했습니다. 이것은 사실이며 내가 사용할 솔루션입니다. 보다 일반적인 용어로, 이것은 자동 추론 시스템에있어 완벽한 문제입니다. 프롤로그는 이러한 시스템을 구성하는 논리 프로그래밍 언어 (및 관련 인터프리터)입니다. 기본적으로 First Order Logic을 사용하여 작성된 진술에서 사실을 결론 지을 수 있습니다 . FOL은 기본적으로보다 진보 된 형태의 제안 논리입니다. Prolog를 사용하지 않기로 결정한 경우 , 결론을 도출하기 위해 modus ponens 와 같은 기술을 사용하여 유사한 자체 제작 시스템을 사용할 수 있습니다 .

물론 어디에서도 언급되지 않았기 때문에 얼룩말에 관한 규칙을 추가해야 할 것입니다 ... 나는 다른 4 마리의 애완 동물을 알아 내서 마지막으로 얼룩말을 추론 할 수 있다고 생각합니다. 당신은 얼룩말이 애완 동물 중 하나이며 각 집마다 애완 동물을 가질 수 있다는 규칙을 추가하고 싶을 것입니다. 이러한 종류의 "상식"지식을 추론 시스템에 도입하는 것은이 기술을 진정한 AI로 사용하는 데있어 가장 큰 장애물입니다. Cyc과 같은 일부 연구 프로젝트는 무차별적인 힘을 통해 그러한 공통 지식을 제공하려고 시도하고 있습니다. 그들은 흥미로운 성공을 거두었습니다.


"상식"규칙에 대한 좋은 지적. 문구 "해석 할 때 나는 정말이 년 전으로 묶여지고 기억 집 옆에 집을"- 하나있다 즉 않습니다 의미? 분명하지 않습니다.
Chris

Dude cyc은 어떤 혁신적인 방법도없이 수십 년 동안 개발되어 왔습니다. 슬프게도, 무차별 대입 접근 방식이 연관 모델을 능가하는 것을 보는 것이 깔끔 할 것입니다.
Josh

우리는 AI 과정에서 이러한 종류의 정보를 추론하기 위해 uni에서 CLIPS를 사용했습니다.
Josh Smeaton

15

SWI- 프롤로그 호환 :

% 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 튜플을 생성합니다.

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

그 중 5 ^ 6, 15625, 쉽게 관리 할 수 ​​있습니다. 그런 다음 간단한 부울 조건을 필터링합니다. 그 중 10 개가 있고 조건의 8/25를 필터링 할 것으로 예상되는 각 조건 (1/25 조건에는 개가있는 스웨덴어가 포함되어 있고 16/25에는 개가 아닌 스웨덴어가 포함되어 있음) . 물론 그것들은 독립적이지 않지만 그것들을 걸러 낸 후에는 많은 것이 남아 있지 않아야합니다.

그 후에는 멋진 그래프 문제가 있습니다. 나머지 n- 튜플 중 하나를 나타내는 각 노드로 그래프를 작성하십시오. 두 개의 끝이 일부 n- 튜플 위치에 중복을 포함하거나 '위치'제약 조건을 위반하는 경우 그래프에 가장자리를 추가합니다 (이 중 5 개가 있음). 거의 모든 곳에서 그래프를 검색하여 독립적 인 5 개 노드 세트 (가장자리에 연결된 노드가없는)를 찾으십시오. 너무 많지 않은 경우 n 튜플의 5 튜플을 모두 철저하게 생성하고 다시 필터링 할 수 있습니다.

이것은 코드 골프에 대한 좋은 후보가 될 수 있습니다. 누군가는 아마도 haskell과 같은 것을 한 줄로 해결할 수 있습니다 :)

나중에 생각할 사항 : 초기 필터 패스는 위치 제약 조건에서 정보를 제거 할 수도 있습니다. 많지 않지만 (1/25) 여전히 중요합니다.


코드 골프의 경우 솔루션은 기술적으로 답변을 인쇄하여 "Hello world"코드 골프와 같습니다. 재미있는 코드 골프를 얻으려면 문제를 일반화해야하지만 사소한 일반화는 아닙니다.
아담 로젠 필드

포인트 촬영 :) 내 haskell은 장황하지만 내 점수는 어쨌든 공원에서 나왔습니다 :)
Chris

1
가능한 해결책에 대한 귀하의 5 ^ 6 평가가 꺼져 있다고 생각합니다. 'm'범주 내에서 'i'항목의 가능한 조합 수는 (i!) ^ (m-1)이어야한다고 생각합니다. 예를 들어, 5 가지 색상 옵션을 5 개로 배열 할 수 있습니다! 방법. 주택 번호 카테고리가 동일한 순서로 제공되면 다른 5 개의 카테고리도 각각 같은 방식으로 정렬 될 수 있습니다. 즉 가능한 조합은 (5!) ^ 5 또는 24,883,200,000입니다. 15,625보다 약간 높으며 무차별 대입 공격을 훨씬 더 어렵게 만듭니다.
MidnightLightning

1
15,625는 그의 솔루션 전략에 따라 정확합니다. 모든 변수에 대해 가능한 모든 상태를 할당하려면 훨씬 더 커지지 만 부분 상태 만 작성하고 칩을 제거한 다음 다른 기술을 사용하여 최종 답변을 작성하십시오.
Nick Larsen

9

이번에는 Python의 PyKE (Python Knowledge Engine)를 사용하는 또 다른 Python 솔루션입니다. 물론, @JFSebastian의 솔루션에서 Python의 "제한"모듈을 사용하는 것보다 더 장황하지만, 이러한 유형의 문제에 대해 원시 지식 엔진을 조사하는 모든 사람에게 흥미로운 비교를 제공합니다.

단서 .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

다음은 C #의 Einstein 's Riddle에 게시 된 NSolver를 사용한 전체 솔루션 에서 발췌 한 내용입니다 .

// 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?-시간 (solve (Z)).
% 111,798 추론, 0.020 초 안에 0.016 CPU (78 % CPU, 7166493 입술)
Z = 독일어.


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


7

ES6 (자바 스크립트) 솔루션

많은 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

이것은 실제로 제약 해결 문제입니다. 언어와 같은 논리 프로그래밍에서 일반화 된 제약 조건 전파로이를 수행 할 수 있습니다. ALE (속성 논리 엔진) 시스템에서 Zebra 문제에 대한 데모를 제공합니다.

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

단순화 된 Zebra 퍼즐 코딩에 대한 링크는 다음과 같습니다.

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

이를 효율적으로 수행하는 것은 또 다른 문제입니다.


3

프로그래밍 방식으로 이러한 문제를 해결하는 가장 쉬운 방법은 모든 순열에 대해 중첩 루프를 사용하고 결과가 문제의 조건자를 만족하는지 확인하는 것입니다. 응답이 적절한 시간 내에 계산 될 수있을 때까지 계산 복잡성을 획기적으로 줄이기 위해 많은 술어가 내부 루프에서 외부 루프로 게양 될 수 있습니다.

다음은 F # Journal 의 기사에서 파생 된 간단한 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 ]

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

https://msdn.microsoft.com/en-us/library/ff525831%28v=vs.93%29.aspx?f=255&MSPPError=-2147217396 의 Microsoft Solver Foundation 예제

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

이것은 Wikipedia에 정의 된 얼룩말 퍼즐에 대한 MiniZinc 솔루션입니다.

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.