Поиск в пространстве состояний
Следующая программа посвящена решению задачи о рыцарях и оруженосцах (Алкуин, 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 оптимальных решений для задачи с пятью миссионерами.