14 февраля 2009 в 22:43

Японская версия головоломки «Волк, коза и капуста» на прологе

Эта головоломка уже знакома Хабрахабру по этой публикации.

Суть головоломки в следующем (цитируя bor1s):
Нужно перевезти семью из шести человек и полицейского с бандитом на другой берег реки на плоту. Однако одновременно на плот помещаются только два человека (уточнение: один из которых должен быть взрослым), мама, оставшись без папы, избивает мальчиков, папа — девочек. А бандит (уточнение: в отсутствие полицейского) просто мочит всех.

Пройти головоломку online можно по ссылке: http://freeweb.siol.net/danej/riverIQGame.swf.

Пролог обычно хорошо справляется с решением таких задач, в чем я и решил убедиться…



<br><br>son(son1).<br>son(son2).<br><br>daughter(daughter1).<br>daughter(daughter2).<br><br><br>adult(father).<br>adult(mother).<br>adult(police).<br><br><br>notsafe_(criminal, X) :- X \= police.<br>notsafe_(mother, Y) :- son(Y).<br>notsafe_(father, Y) :- daughter(Y).<br><br>notsafe(X, Y) :- notsafe_(X, Y); notsafe_(Y, X).<br><br>safe(X, Y) :- \+ notsafe(X, Y).<br><br>safebridge([X, Y]) :- (adult(X); adult(Y)), safe(X, Y), !.<br>safebridge([X]) :- adult(X).<br><br><br>all([<br>     son1, son2, father, <br>     daughter1, daughter2, mother,<br>     criminal, police<br>    ]).<br><br><br>allsafe(L) :-<br>    forall(member(H, L),<br>          ( adult(H)<br>          ; son(H), <br>          ( member(mother, L)<br>          -> member(father, L)<br>          ; true<br>          )<br>          ; daughter(H),<br>          ( member(father, L)<br>          -> member(mother, L)<br>          ; true<br>          )<br>          ; H = criminal, member(police, L) <br>          )), !.<br>allsafe([_]).<br>allsafe([]).<br><br>allPairs([H | T], [H, P2]) :-<br> member(P2, T).<br><br>allPairs([_ | T], P) :-<br> allPairs(T, P).<br>    <br>step_(state(Left1, left), state(Left2, right)) :-<br>    ( allPairs(Left1, OnBridge)<br>    ; member(A, Left1),<br>        OnBridge = [A]<br>    ),<br>    <br>    safebridge(OnBridge),<br>    <br>    subtract(Left1, OnBridge, Left2),<br>    allsafe(Left2),<br>    <br>    all(All),<br>    subtract(All, Left2, Right),<br>    allsafe(Right).<br><br>step(state(Left1, left), state(Left2, right)) :-<br>    step_(state(Left1, left), state(Left2, right)).<br><br>step(state(Left1, right), state(Left2, left)) :-<br>    all(All),<br>    subtract(All, Left1, Right1),<br>    step_(state(Right1, left), state(Right2, right)),<br>    subtract(All, Right2, Left2).<br><br>notequal(state(L1, P1), state(L2, P2)) :-<br>    \+ (<br>       P1 = P2,<br>        sort(L1, L),<br>        sort(L2, L)<br>       ).<br>solve(Inp, Outp, PrevSteps, [Step | Steps]) :-<br>    Step = step(Inp, S1), <br>    Step, forall(member(step(State1, _), PrevSteps), notequal(State1, S1)), % to prevent loops <br>    <br>    ( S1 = Outp<br>    -> Steps = []<br>    ; solve(S1, Outp, [Step | PrevSteps], Steps)<br>    ).<br><br>solve :-<br>    all(All),<br>    findall(Steps, solve(state(All, left), state([], _), [], Steps), Solutions),<br>    length(Solutions, SolLength),<br>    format('Found ~w solutions:~n', [SolLength]),<br>    forall(member(Solution, Solutions),<br>          ( format('~nSolution:~n'), <br>          forall(member(Step, Solution), <br>             printStep(Step)<br>            )<br>          )).<br><br>printStep(step(state(L1, Pos1), state(L2, _))) :-<br>    ( Pos1 = left<br>    -> subtract(L1, L2, Moved), <br>        format('~w -> left: ~w~n', [Moved, L2])<br>    ; Pos1 = right<br>    -> subtract(L2, L1, Moved),<br>        format('~w <- left: ~w~n', [Moved, L2])<br>    ).<br><br><br><br><br><br><br><br><br><br><br><br><br>


Программа очень быстро находит и выводит 8 вариантов перехода через реку.

Разжевывать работу программы сильно не охота, но алгоритм вкратце можно описать следующим образом. Состояние системы зададим в виде state(L, Pos), где L — список людей с левой стороны моста, а Pos — местоположение плота (left, right).
В программе описан предикат step(S1, S2) который описывает возможные изменения состояния системы. Для решения проблемы присутствует предикат solve, который упрощенно можно записать как

solve(S1, S2) :-
step(S1, S), % делаем шаг
( S = S2 % если конечное состояние достигнуто, то решение найдено
; solve(S, S2) % иначе, решаем дальше
).


Этот предикат, используя переборную природу пролога делает возможные (в описанных ограничениях, приведенных в начале программы) шаги, пока не достигнет конечного состояния state([], _) (слева никого не осталось). Впрочем, в предикат пришлось ввести проверку на то, что мы не попали в уже пройденное состояние, иначе программа, очевидным образом, зациклится.

Полюбоваться на варианты решения головоломки на Haskell, J, Ruby можно на форуме RSDN. Так же, если интересно, предлагаю написать решение на вашем любимом ЯП.

Дополнение. О решении другой задачки тоже на переход через реку (Побег от Зурга) можно почитать здесь. Статья рассказывает о том, что Haskell тоже довольно удобен для решения поисковых задач, наравне с традиционным в этой области Prolog'ом. Свое решение этой задачки (чуть сложнее чем в статье) на прологе я разместил здесь.
Владимир Губарьков @xonix
карма
131,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (3)

  • +4
    >избивает мальчиков, папа — девочек. А бандит просто мочит всех.
    как мило ^_^
    • 0
      хоть людей и больше, но не составляет труда пройти.
  • +1
    Еще одна хорошая интерпретация этой игры

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.