Поиск в пространстве состояний
Следующая программа посвящена решению задачи о рыцарях и оруженосцах (Алкуин, VIII в.). Три рыцаря, каждый со своим оруженосцем, хотят переправиться через реку, с левого берега на правый. В их распоряжении имеется двухместная лодка. Ни один оруженосец не может оставаться где-либо без своего рыцаря в присутствии других рыцарей. Как им переправиться на другой берег?
Имеются варианты задачи для четырех или пяти рыцарей и трехместной лодки. Двухместной лодки в этом случае недостаточно. Если рыцарей шесть и более, то недостаточно и трехместной лодки. Четырехместной лодки достаточно для переправы любого количества рыцарей.
Для того чтобы можно было использовать предикат subset в другом модуле, его объявление нужно перенести в декларацию класса ex1 (листинг 13.4).
predicates
subset : (positive, A*, A* [out], A* [out]) nondeterm.
Пример
13.4.
Декларация класса ex1
open core, console, list, string, depth, ex1
class facts
n : positive := 0.
capacity : positive := 2. % вместимость лодки
domains
knightOrSquire = k(integer); s(integer).
kstate = tuple(loc, knightOrSquire*, knightOrSquire*).
loc = left; right.
class predicates
kmove : move{kstate}.
moveFromTo : (positive, knightOrSquire*, knightOrSquire*,
knightOrSquire* [out], knightOrSquire* [out],
knightOrSquire* [out]) nondeterm.
unsafe: (knightOrSquire*) determ.
f: (knightOrSquire*) -> string.
g: (knightOrSquire) -> string.
clauses
kmove(tuple(left, L, R), Str) = tuple(right, L1, R1):-
K = std::downTo(capacity, 2),
moveFromTo(K, L, R, L1, R1, B),
Str = format("% from left to right", f(B)).
kmove(tuple(right, L, R), Str) = tuple(left, L1, R1):-
K = std::fromTo(1, capacity),
moveFromTo(K, R, L, R1, L1, B),
Str = format("% from right to left", f(B)).
moveFromTo(N, FromBef, ToBefore, FromAfter, ToAfter, Boat):-
subset(N, FromBef, Boat, FromAfter),
not(unsafe(Boat)),
not(unsafe(FromAfter)),
ToAfter = sort(append(Boat, ToBefore)),
not(unsafe(ToAfter)).
unsafe(L):-
s(N) = getMember_nd(L),
not(k(N) = getMember_nd(L)),
k(_) = getMember_nd(L),
!.
f([S]) = format("% moves", g(S)):- !.
f(L) = format("% move",
concatWithDelimiter(map(L, {(X) = g(X)}), ", ")).
g(k(N)) = concat("knight ", toString(N)).
g(s(N)) = concat("squire ", toString(N)).
run():-
L = sort([s(1), k(1), s(2), k(2), s(3), k(3)]),
N = list::length(L) div 2,
capacity := if N < 4 then 2 elseif N < 6 then 3 else 4 end if,
Start = tuple(left, L, []),
Goal = tuple(right, [], L),
tuple([S0 | P], Moves) = depthSearch(kmove, Start, Goal, _W),
V = varM::new(1),
n := n + 1,
write("\t", S0), nl,
forAll(zip(P, Moves), {(tuple(X, S)):-
writef("%. %\n\t%\n", V:value, S, X),
V:value := V:value + 1}), nl,
fail;
write("\nвсего - ", n),
_ = readLine().
Пример
13.5.
Задача о рыцарях и оруженосцах. Модуль ex2
Предикат downTo недетерминированно возвращает целые числа в указанных пределах в порядке убывания. Предикат list::length/1 возвращает количество элементов списка.
Для трех рыцарей задача имеет 486 оптимальных решений, которые содержат по 11 переходов. Для четырех рыцарей и трехместной лодки существует 15600 различных оптимальных решений, они содержат по 9 переходов.
Следующая программа посвящена решению задачи о миссионерах и каннибалах (XIX в.). Три миссионера и три каннибала хотят переправиться через реку с левого берега на правый. В их распоряжении имеется двухместная лодка. Если где-либо каннибалов будет больше, чем миссионеров, то они их съедят. Как они могут переправиться на другой берег?
Как и в предыдущем случае, имеются варианты задачи для четырех или пяти миссионеров (каннибалов столько же, сколько и миссионеров) и трехместной лодки. Если миссионеров более пяти, то лодка должна быть четырехместная.
open core, console, list, string, depth
class facts
n : positive := 0.
capacity : positive := 2. % вместимость лодки
domains
mstate = tuple(loc, positive*, positive*).
loc = left; right.
class predicates
mcmove : move{mstate}.
mcmoveFromTo: (positive, positive, positive*, positive*,
positive* [out], positive* [out]) determ.
f: (positive, positive) -> string.
g: (positive) -> string.
b: (positive, positive) determ.
clauses
mcmoveFromTo(Mb, Cb, [MFromBefore, CFromBefore],
[MToBefore, CToBefore], [MFromAfter, CFromAfter],
[MToAfter, CToAfter]):-
Cb <= CFromBefore,
Mb <= MFromBefore,
b(Mb, Cb),
MFromAfter = MFromBefore - Mb,
CFromAfter = CFromBefore - Cb,
b(MFromAfter, CFromAfter),
MToAfter = MToBefore + Mb,
CToAfter = CToBefore + Cb,
b(MToAfter, CToAfter).
mcmove(tuple(left, L, R), Str) = tuple(right, L1, R1):-
K = std::downTo(capacity, 2),
Cb = std::downTo(K, 0),
Mb = K - Cb,
mcmoveFromTo(Mb, Cb, L, R, L1, R1),
Str = format("% % from left to right", f(Mb, Cb), g(Mb + Cb)).
mcmove(tuple(right, L, R), Str) = tuple(left, L1, R1):-
K = std::fromTo(1, capacity),
Cb = std::fromTo(0, K),
Mb = K - Cb,
mcmoveFromTo(Mb, Cb, R, L, R1, L1),
Str = format("% % from right to left", f(Mb, Cb), g(Mb + Cb)).
f(0, 1) = "1 cannibal":- !.
f(0, C) = format("% cannibals", C):- !.
f(1, 0) = "1 missionary":- !.
f(M, 0) = format("% missionaries", M):- !.
f(M, C) = format("%, %", f(M, 0), f(0, C)).
g(1) = "moves":- !.
g(_) = "move".
b(M, C):-
if M > 0 then M >= C end if.
run():-
Start = tuple(left, [3, 3], [0, 0]),
Goal = tuple(right, [0, 0], [3, 3]),
capacity := 2,
tuple([S0 | P], Moves) = depthSearch(mcmove, Start, Goal, _),
V = varM::new(1),
n := n + 1,
write("\t", S0), nl,
forAll(zip(P, Moves), {(tuple(X, S)):-
writef("%. %\n\t%\n", V:value, S, X),
V:value := V:value + 1}), nl,
fail;
write("\nвсего - ", n),
_ = readLine().
Пример
13.6.
Задача о миссионерах и каннибалах. Модуль ex3
Предикат zip/2 для двух списков одинаковой длины возвращает список, составленный из пар элементов из этих списков с одинаковыми индексами.
Имеется четыре оптимальных решения для задачи о трех миссионерах, 32 оптимальных решения для задачи о четырех миссионерах и 25 оптимальных решений для задачи с пятью миссионерами.