Решение головоломки Зебра (также известной как головоломка Эйнштейна) с использованием библиотеки Prolog clpfd

Мне дали упражнение по решению загадки зебры с использованием решателя ограничений по моему выбору, и я попробовал его, используя Библиотека Prolog clpfd.

Я знаю, что есть и другие, более идиоматические способы решения этой проблемы в Prolog, но этот вопрос касается конкретно пакета clpfd!

Вот конкретный вариант головоломки (учитывая, что их много), который я пытаюсь решить:

Всего пять домов

  1. Англичанин живет в красном доме
  2. У шведов есть собака
  3. Датчанин любит пить чай
  4. Зеленый дом оставлен белому дому
  5. Хозяин зеленого дома пьет кофе
  6. Человек, который курит Pall Mall, владеет птицей.
  7. В среднем доме пьют молоко
  8. Хозяин желтого дома курит Dunhill
  9. Норвежец живет в первом доме
  10. Курильщик мальборо живет рядом с хозяином кошки
  11. Владелец лошади живет рядом с человеком, который курит данхилл.
  12. Курильщик winfield любит пить пиво
  13. Норвежец живет рядом с синим домом
  14. Немец курит ротманны
  15. У курильщика мальборо есть сосед, который пьет воду

Я попытался решить эту проблему следующим образом:

Каждый атрибут, который может иметь дом, моделируется как переменная, например «Британский», «Собака», «Зеленый» и т. Д. Атрибуты могут принимать значения от 1 до 5, в зависимости от дома, в котором они встречаются, например если переменная «Собака» принимает значение 3, собака живет в третьем доме.

Такой подход упрощает моделирование ограничений соседей следующим образом:

def neighbor(X, Y) :-
    (X #= Y-1) #\/ (X #= Y+1).

Но почему-то пакет clpfd не дает решения, хотя (ИМО) проблема смоделирована правильно (я использовал ту же модель с Решатель ограничений Choco, и результат был правильным).

Вот полный код:

:- use_module(library(clpfd)).

neighbor(X, Y) :-
    (X #= (Y - 1)) #\/ (X #= (Y + 1)).

solve([British, Swedish, Danish, Norwegian, German], Fish) :-

    Nationalities = [British, Swedish, Danish, Norwegian, German],
    Colors = [Red, Green, Blue, White, Yellow],
    Beverages = [Tea, Coffee, Milk, Beer, Water],
    Cigarettes = [PallMall, Marlboro, Dunhill, Winfield, Rothmanns],
    Pets = [Dog, Bird, Cat, Horse, Fish],

    all_different(Nationalities),
    all_different(Colors),
    all_different(Beverages),
    all_different(Cigarettes),
    all_different(Pets),

    Nationalities ins 1..5,
    Colors ins 1..5,
    Beverages ins 1..5,
    Cigarettes ins 1..5,
    Pets ins 1..5,

    British #= Red, % Hint 1
    Swedish #= Dog, % Hint 2
    Danish #= Tea, % Hint 3

    Green #= White - 1 , % Hint 4
    Green #= Coffee, % Hint 5,
    PallMall #= Bird, % Hint 6

    Milk #= 3, % Hint 7
    Yellow #= Dunhill, % Hint 8,
    Norwegian #= 1, % Hint 9

    neighbor(Marlboro, Cat), % Hint 10
    neighbor(Horse, Dunhill), % Hint 11
    Winfield #= Beer, % Hint 12

    neighbor(Norwegian, Blue), % Hint 13
    German #= Rothmanns, % Hint 14,
    neighbor(Marlboro, Water). % Hint 15

Я неправильно понял концепцию в clpfd, или я просто упускаю здесь что-то очевидное? Если это поможет, здесь вы можете найти то же самое подход реализован с использованием Choco и Scala.


Изменить: Причина, по которой я считаю, что решатель не может решить проблему, заключается в том, что он никогда не дает определенных значений для переменных, а только с диапазонами, например «Рыба 1..3 \ / 5».


person fresskoma    schedule 20.06.2012    source источник
comment
Используйте метку (Vars) или метку (Options, Vars), чтобы решить проблему.   -  person joel76    schedule 20.06.2012
comment
label работает только в том случае, если есть определенный результат, тогда как то, что я получаю для каждой переменной, - это только диапазоны (как объяснено в редактировании).   -  person fresskoma    schedule 20.06.2012
comment
Если ваш CLP (FD) будет иметь Expr в Set, тогда можно формализовать соседа (X, Y): - X - Y в [-1,1].   -  person Mostowski Collapse    schedule 21.06.2012
comment
Вы можете определить сосед / 2 как: abs (X-Y) # = 1.   -  person mat    schedule 21.06.2012
comment
@CookieMonster: Вы можете написать V in -1\/1, но если разрешить выражения для первого аргумента, это сделает его дефолтным.   -  person false    schedule 23.06.2012


Ответы (2)


запустив ваш код в SWI-Prolog, я получаю

?- solve(X),label(X).
X = [3, 5, 2, 1, 4].

Без label:

?- solve(X).
X = [3, _G3351, _G3354, 1, _G3360],
_G3351 in 4..5,
all_different([_G3351, _G3386, _G3389, 2, _G3395]),
all_different([3, _G3351, _G3354, 1, _G3360]),
_G3386 in 3..5,
all_different([_G3386, _G3444, 1, _G3450, _G3360]),
_G3389 in 1\/3..5,
_G3389+1#=_G3478,
_G3492+1#=_G3389,
_G3395 in 1\/3..5,
_G3478 in 2..6,
_G3444#=_G3478#<==>_G3529,
_G3444 in 2..5,
_G3444#=_G3556#<==>_G3553,
_G3444#=_G3568#<==>_G3565,
_G3444#=_G3492#<==>_G3577,
_G3450 in 2\/5,
all_different([_G3354, 4, 3, _G3450, _G3614]),
_G3360 in 2\/4..5,
_G3354 in 2\/5,
_G3614 in 1..2\/5,
_G3614+1#=_G3556,
_G3568+1#=_G3614,
_G3556 in 2..3\/6,
_G3553 in 0..1,
_G3565#\/_G3553#<==>1,
_G3565 in 0..1,
_G3568 in 0..1\/4,
_G3492 in 0..4,
_G3577 in 0..1,
_G3577#\/_G3529#<==>1,
_G3529 in 0..1.

Если я изменю all_different на all_distinct, я получу решение без ярлыка:

....
all_distinct(Nationalities),
all_distinct(Colors),
all_distinct(Beverages),
all_distinct(Cigarettes),
all_distinct(Pets),
....

?- solve(X).
X = [3, 5, 2, 1, 4].

Как видите, в документации указано более сильное распространение для all_distinct vs all_different. Запуск предложенного образца поможет понять разницу между ними:

?- maplist(in, Vs, [1\/3..4, 1..2\/4, 1..2\/4, 1..3, 1..3, 1..6]), all_distinct(Vs).
false.

?- maplist(in, Vs, [1\/3..4, 1..2\/4, 1..2\/4, 1..3, 1..3, 1..6]), all_different(Vs).
Vs = [_G419, _G422, _G425, _G428, _G431, _G434],
_G419 in 1\/3..4,
all_different([_G419, _G422, _G425, _G428, _G431, _G434]),
_G422 in 1..2\/4,
_G425 in 1..2\/4,
_G428 in 1..3,
_G431 in 1..3,
_G434 in 1..6.
person CapelliC    schedule 20.06.2012
comment
all_distinct сделал свое дело. Спасибо. Я всегда пытался сделать что-то вроде solve(X,Y) (см. Обновленную подпись решения) и получил ERROR: Arguments are not sufficiently instantiated, но этого не происходит при использовании all_distinct. - person fresskoma; 20.06.2012
comment
На первый взгляд я заметил, что Fish был синглтоном в исходном коде, но, возившись с кодом, я не получил тех последствий, которые вы правильно обнаружили. - person CapelliC; 20.06.2012
comment
all_distinct очень медленнее, чем all_different, за которым следует label во всех случаях, которые я тестировал. - person fpg1503; 26.10.2015
comment
@ fpg1503: Я их не тестировал. Если у вас есть пример, который можно опубликовать, может быть интересно составить диаграмму улучшения «силы» по сравнению с повышенной сложностью для обоих алгоритмов. - person CapelliC; 26.10.2015

Здесь есть несколько заблуждений: вы заявляете, что «пакет clpfd не дает решения», но на самом деле он дает одно:

?- solve(Ls, Fish), label(Ls).
Ls = [3, 5, 2, 1, 4],
Fish in 1\/4,
all_different([5, 3, _G3699, 2, Fish]),
_G3699 in 1\/4,
_G3699+1#=_G3727,
_G3741+1#=_G3699,
_G3727 in 2\/4..5,
2#=_G3727#<==>_G3766,
_G3766 in 0..1,
_G3792#\/_G3766#<==>1,
_G3792 in 0..1,
2#=_G3741#<==>_G3792,
_G3741 in 0\/2..3.

Итак, мы знаем, что если есть решение, тогда Fish будет либо 1, либо 4. Давайте попробуем 1:

?- solve(Ls, Fish), label(Ls), Fish = 1.
false.

Нет. Так что давайте попробуем 4:

?- solve(Ls, Fish), label(Ls), Fish = 4.
Ls = [3, 5, 2, 1, 4],
Fish = 4.

Это работает и является основным решением проблемы. Вы можете получить это другим способом, например, включив Fish в переменные, которые нужно пометить:

?- solve(Ls, Fish), label([Fish|Ls]).
Ls = [3, 5, 2, 1, 4],
Fish = 4 ;
false.

Целью маркировки является именно проверка конкретных значений ограниченных переменных, независимо от того, существует ли на самом деле решение. По совпадению all_distinct / 1 достаточно силен, чтобы дать базовое решение в этом случае, но в целом это, конечно, не так, и вы должны в конечном итоге использовать маркировку, чтобы получить безусловный (то есть больше не ожидающих ограничений) ответ. Конечно, тогда вы должны также пометить все переменные, которые вас интересуют, а не только их подмножество, как вы это делали изначально. Чтобы пометить одну переменную, вы можете использовать indomain / 1, поэтому добавление indomain (Fish) к первому запросу выше также будет работать. Мне не удалось воспроизвести ошибку создания экземпляра, которую вы упомянули в следующем комментарии, на самом деле, как вы видите выше, наиболее общее решение запроса (X, Y) работает с кодом, который вы опубликовали. Наконец, проверьте это:

neighbor(X, Y) :- abs(X-Y) #= 1.
person mat    schedule 20.06.2012