Настоящая глава посвящена решению задач при помощи графа пространства состояний. Пространство состояний описывается в виде множества состояний — вершин графа, множества переходов от состояния к состоянию — дуг графа, множества начальных состояний и множества конечных состояний. Решение задачи представляется в виде пути на графе пространства состояний, соединяющего начальное состояние с конечным.
Если пространство состояний задачи невелико, то будут находиться все оптимальные решения с помощью поиска в глубину. В задачах с большим пространством состояний будет вычисляться только одно оптимальное решение посредством поиска в ширину.
К задачам применяются универсальные решатели. Состояния в разных задачах могут принадлежать различным доменам. Для запоминания наилучших среди найденных решений используется "изменяемая переменная" varM. Компилятор сам находит нужные типы.
В настоящем параграфе поиск в глубину в пространстве состояний применяется для решения известных задач. Сначала решатель применяется к задаче о волке, козе и капусте. Для решения других задач в программу добавляются лишь описания состояний и переходов между ними.
Задача о волке, козе и капусте заключается в следующем (Алкуин, XIII в.). Хозяин с волком, козой и грудой кочанов капусты должен перебраться через реку с левого берега на правый, имея в распоряжении маленькую лодку. В эту лодку, кроме хозяина, может поместиться только что-то одно — либо волк, либо коза, либо капуста. Нельзя оставлять без присмотра волка с козой, а козу с капустой. Как следует организовать переправу?
Состояния в задачах о перевозке через реку обычно описываются в виде тройки, содержащей множество существ (предметов) на левом берегу, множество существ (предметов) на правом берегу, и берег, у которого находится лодка.
Для решения задач, как и ранее, создается консольный проект. В этом проекте создается модуль depth. В этот модуль следует поместить универсальный решатель, который использует поиск в глубину (листинги 13.1–13.2V = varM::new([]), в версии Visual Prolog 7.4 нужно заменить строкой V = varM{tuple{State*, string*}*}::new([]),
domains
move{State} = (State, string [out]) -> State nondeterm.
predicates
depthSearch: (move{State}, State, State, positive [out])
-> tuple{State*, string*} nondeterm.
open core, list
class facts
minweight : positive := 2^30.
class predicates
depthSearch: (move{State}, State, State, tuple{State*, string*},
positive, positive [out]) -> tuple{State*, string*} nondeterm.
clauses
depthSearch(Move, Start, Goal, minweight) =
tuple(reverse(Path), reverse(Moves)):-
minweight := 2^30,
V = varM::new([]),
foreach tuple(P, Ms) =
depthSearch(Move, Start, Goal, tuple([Start], []), 0, N)
do
if N > minweight then succeed()
elseif N < minweight then
V:value := [tuple(P, Ms)], minweight := N
else V:value := [tuple(P, Ms) | V:value]
end if
end foreach,
tuple(Path, Moves) = getMember_nd(V:value).
depthSearch(_, Goal, Goal, Path, N, N) = Path:- !.
depthSearch(Move, State, Goal, tuple(Path, Moves), C, N) =
depthSearch(Move, NextState, Goal, tuple([NextState | Path],
[Str | Moves]), C + 1, N):-
C < minweight,
NextState = Move(State, Str),
not(isMember(NextState, Path)).
Далее, как обычно, создаются модули для примеров.
open core, console, list, depth
class facts
n : positive := 0.
domains
wstate = tuple(loc BoatLoc, string* LeftSide, string* RightSide).
loc = left; right.
class predicates
wmove : move{wstate}.
unsafestate : (string*) determ.
wmoveFromTo: (positive, string*, string*, string* [out],
string* [out], string* [out]) nondeterm.
subset : (positive, A*, A* [out], A* [out]) nondeterm.
f: (string*) -> string.
clauses
wmoveFromTo(K, FromBef, ToBefore, FromAfter, ToAfter, Boat):-
subset(K, FromBef, Boat, FromAfter),
not(unsafestate(FromAfter)),
ToAfter = sort(append(Boat, ToBefore)).
wmove(tuple(left, L, R), Str) = tuple(right, L1, R1):-
wmoveFromTo(1, L, R, L1, R1, B),
Str = string::format("farmer% moves from left to right", f(B)).
wmove(tuple(right, L, R), Str) = tuple(left, L1, R1):-
K = std::fromTo(0, 1),
wmoveFromTo(K, R, L, R1, L1, B),
Str = string::format("farmer% moves from right to left", f(B)).
f([S]) = string::format(" with %", S):- !.
f(_) = "".
unsafestate(L):-
L = [_, _ | _],
isMember("goat", L).
subset(0, L, [], L):- !.
subset(K, [A | L], [A | L1], L2):-
subset(K - 1, L, L1, L2).
subset(K, [A | L], L1, [A | L2]):-
subset(K, L, L1, L2).
run():-
L = sort(["wolf", "goat", "cabbage"]),
Start = tuple(left, L, []),
Goal = tuple(right, [], L),
tuple([S0 | P], Moves) = depthSearch(wmove, 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().
Следующая программа посвящена решению задачи о рыцарях и оруженосцах (Алкуин, VIII в.). Три рыцаря, каждый со своим оруженосцем, хотят переправиться через реку, с левого берега на правый. В их распоряжении имеется двухместная лодка. Ни один оруженосец не может оставаться где-либо без своего рыцаря в присутствии других рыцарей. Как им переправиться на другой берег?
Имеются варианты задачи для четырех или пяти рыцарей и трехместной лодки. Двухместной лодки в этом случае недостаточно. Если рыцарей шесть и более, то недостаточно и трехместной лодки. Четырехместной лодки достаточно для переправы любого количества рыцарей.
Для того чтобы можно было использовать предикат subset в другом модуле, его объявление нужно перенести в декларацию класса ex1 (листинг 13.4).
predicates
subset : (positive, A*, A* [out], A* [out]) nondeterm.
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().
Предикат 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().
Предикат zip/2 для двух списков одинаковой длины возвращает список, составленный из пар элементов из этих списков с одинаковыми индексами.
Имеется четыре оптимальных решения для задачи о трех миссионерах, 32 оптимальных решения для задачи о четырех миссионерах и 25 оптимальных решений для задачи с пятью миссионерами.
В настоящем параграфе для решения задач применяется поиск в ширину в пространстве состояний. Для ограничения пространства поиска решения не продлеваются в ранее достигнутые состояния. Ведется поиск только одного оптимального решения.
Ниже приводится универсальный решатель задач методом поиска в ширину в пространстве состояний. Он реализуется в модуле breadth (листинги 13.7–13.8V = varM::new([]),
в версии Visual Prolog 7.4 нужно заменить строкой V = varM{State*}::new([]).
predicates
breadthSearch: (depth::move{State}, State, State, positive [out])
-> tuple{State*, string*} determ.
open core, list, depth
domains
t{State} = t(State*, string*, positive).
class predicates
breadthPath: (move{State}, t{State}*, State*, State)
-> t{State} nondeterm.
clauses
breadthSearch(Move, Start, Goal, N) =
tuple(reverse(Path), reverse(Moves)):-
t(Path, Moves, N) =
breadthPath(Move, [t([Start], [], 0)], [Start], Goal),
!.
breadthPath(_, [t([Goal | Path], Ms, N) | _], _, Goal) =
t([Goal | Path], Ms, N):- !.
breadthPath(Move, [t([State|Path], Ms, N) | PL], StateList, Goal)=
breadthPath(Move, append(PL, PL1),
append(V:value, StateList), Goal):-
V = varM::new([]),
PL1 = [t([NextState, State | Path], [Str | Ms], N + 1) ||
NextState = Move(State, Str),
not(isMember(NextState, StateList)),
V:value := [NextState | V:value]].
Применим решатель к задаче о переливаниях. Имеется несколько сосудов, вмещающих целое количество литров воды, и источник воды (река). Требуется отмерить в первом сосуде заданное целое количество литров воды. Можно наливать воду в сосуды из источника, переливать воду из одного сосуда в другой, так чтобы либо первый сосуд становился пустым, либо второй сосуд полным, а также опорожнять сосуды. Обычно считается, что сосуды упорядочены по убыванию вместимости. В этом случае в первом сосуде можно отмерить любое целое количество литров, которое делится на наибольший общий делитель объемов всех сосудов. Потребуем, чтобы нужное количество литров отмерялось в первом, наибольшем сосуде, а остальные сосуды оказывались бы пустыми. Если сосудов два, то можно показать, что задача в этом случае имеет единственное оптимальное решение. Если сосудов более двух, то оптимальных решений более одного. Пространство состояний в этом случае велико, поэтому мы будем искать только одно оптимальное решение с помощью поиска в ширину.
open core, console, list, string, breadth
class facts
volums: (positive*) determ.
clauses
volums([17, 11, 8]). % volums([27, 17, 11, 8, 3]).
class predicates
jmove : depth::move{positive*}.
clauses
jmove(L, Str) = L1:-
memberIndex_nd(A, I, L),
A > 0,
setNth(I, L, 0, L1),
Str = format("Вылить % л из %-го сосуда", A, I + 1).
jmove(L, Str) = L2:-
volums(VL),
memberIndex_nd(A, I, L),
A > 0,
memberIndex_nd(B, J, L), J <> I,
V = nth(J, VL),
B < V,
Vol = math::min(V - B, A),
setNth(I, L, A - Vol, L1),
setNth(J, L1, B + Vol, L2),
Str = format("Перелить % л из %-го сосуда в %-й",
Vol, I + 1, J + 1).
jmove(L, Str) = L1:-
volums(VL),
memberIndex_nd(A, I, L),
V = nth(I, VL),
A < V,
setNth(I, L, V, L1),
Str = format("Налить % л в %-й сосуд", V - A, I + 1).
run():-
Start = [0, 0, 0],
Goal = [4, 0, 0],
tuple([S0 | P], Moves) = breadthSearch(jmove, Start, Goal, _),
V = varM::new(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;
_ = readLine().
Предикат memberIndex_nd/3 недетерминированно возвращает из списка элементы вместе с их индексами. Предикат setNth/4 заменяет элемент списка, стоящий в заданной позиции, заданным элементом. Предикат nth/2 возвращает элемент списка по его индексу. Предикат min/2 возвращает минимум двух чисел.
Используем поиск в ширину для решения задачи о переправе рыцарей и оруженосцев через реку с островом. Она отличается от первоначальной задачи тем, что на реке есть остров, на который можно высаживаться. Известно, что в этом случае решение существует для любого количества пар рыцарей и оруженосцев и двухместной лодки.
Следует перенести указанные ниже объявления в декларацию класса ex2 из его имплементации (листинг 13.10).
domains
knightOrSquire = k(integer); s(integer).
predicates
moveFromTo: (positive, knightOrSquire*, knightOrSquire*,
knightOrSquire* [out], knightOrSquire* [out],
knightOrSquire* [out]) nondeterm.
f: (knightOrSquire*) -> string.
open core, console, list, string, breadth, ex2
domains
kistate = tuple(loc, knightOrSquire*, knightOrSquire*,
knightOrSquire*).
loc = left; right; island.
class predicates
kimove : depth::move{kistate}.
clauses
kimove(tuple(left, L, Isl, R), Str) = tuple(right, L1, Isl, R1):-
moveFromTo(2, L, R, L1, R1, B),
Str = format("% from left to right", f(B)).
kimove(tuple(island, L, Isl, R), Str) = tuple(right, L, Isl1, R1):-
moveFromTo(2, Isl, R, Isl1, R1, B),
Str = format("% from island to right", f(B)).
kimove(tuple(left, L, Isl, R), Str) = tuple(island, L1, Isl1, R):-
moveFromTo(2, L, Isl, L1, Isl1, B),
Str = format("% from left to island", f(B)).
kimove(tuple(right, L, Isl, R), Str) = tuple(left, L1, Isl, R1):-
moveFromTo(1, R, L, R1, L1, B),
Str = format("% from right to left", f(B)).
kimove(tuple(island, L, Isl, R), Str) = tuple(left, L1, Isl1, R):-
moveFromTo(1, Isl, L, Isl1, L1, B),
Str = format("% from island to left", f(B)).
kimove(tuple(right, L, Isl, R), Str) = tuple(island, L, Isl1, R1):-
moveFromTo(1, R, Isl, R1, Isl1, B),
Str = format("% from right to island", f(B)).
run():-
L = sort([s(1), k(1), s(2), k(2), s(3), k(3), s(4), k(4)]),
Start = tuple(left, L, [], []),
Goal = tuple(right, [], [], L),
tuple([S0 | P], Moves) = breadthSearch(kimove, Start, Goal,_),
V = varM::new(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;
_ = readLine().
Наконец, используем поиск в ширину для решения задачи о переправе мисиионеров и каннибалов через реку с островом (конец XIX в.). Если на реке имеется остров, на который можно высаживаться, то переправу можно организовать также для любого количества миссионеров и такого же количества каннибалов и двухместной лодки.
Указанные ниже объявления следует перенести в декларацию класса ex3 из имплементации этого класса (см. листинг 13.12).
predicates
mcmoveFromTo: (positive, positive, positive*, positive*,
positive* [out], positive* [out]) determ.
f: (positive, positive) -> string.
open core, console, list, string, breadth, ex3
domains
mistate = tuple(loc, positive*, positive*, positive*).
loc = left; right; island.
class predicates
mimove : depth::move{mistate}.
clauses
mimove(tuple(left, L, Isl, R), Str) = tuple(right, L1, Isl, R1):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, L, R, L1, R1),
Str = format("% move from left to right", f(Mb, Cb)).
mimove(tuple(left, L, Isl, R), Str) = tuple(island, L1, Isl1, R):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, L, Isl, L1, Isl1),
Str = format("% move from left to island", f(Mb, Cb)).
mimove(tuple(island, L, Isl, R), Str) = tuple(right, L, Isl1, R1):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, Isl, R, Isl1, R1),
Str = format("% move from island to right", f(Mb, Cb)).
mimove(tuple(right, L, Isl, R), Str) = tuple(left, L1, Isl, R1):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, R, L, R1, L1),
Str = format("% moves from right to left", f(Mb, Cb)).
mimove(tuple(right, L, Isl, R), Str) = tuple(island, L, Isl1, R1):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, R, Isl, R1, Isl1),
Str = format("% moves from right to island", f(Mb, Cb)).
mimove(tuple(island, L, Isl, R), Str) = tuple(left, L1, Isl1, R):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, Isl, L, Isl1, L1),
Str = format("% moves from island to left", f(Mb, Cb)).
run():-
N = 4,
Start = tuple(left, [N, N], [0, 0], [0, 0]),
Goal = tuple(right, [0, 0], [0, 0], [N, N]),
tuple([S0| P], Moves) = breadthSearch(mimove, Start, Goal,_),
V = varM::new(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;
_ = readLine().
Настоящая глава посвящена решению задач при помощи графа пространства состояний. Пространство состояний описывается в виде множества состояний — вершин графа, множества переходов от состояния к состоянию — дуг графа, множества начальных состояний и множества конечных состояний. Решение задачи представляется в виде пути на графе пространства состояний, соединяющего начальное состояние с конечным.
Если пространство состояний задачи невелико, то будут находиться все оптимальные решения с помощью поиска в глубину. В задачах с большим пространством состояний будет вычисляться только одно оптимальное решение посредством поиска в ширину.
К задачам применяются универсальные решатели. Состояния в разных задачах могут принадлежать различным доменам. Для запоминания наилучших среди найденных решений используется "изменяемая переменная" varM. Компилятор сам находит нужные типы.
В настоящем параграфе поиск в глубину в пространстве состояний применяется для решения известных задач. Сначала решатель применяется к задаче о волке, козе и капусте. Для решения других задач в программу добавляются лишь описания состояний и переходов между ними.
Задача о волке, козе и капусте заключается в следующем (Алкуин, XIII в.). Хозяин с волком, козой и грудой кочанов капусты должен перебраться через реку с левого берега на правый, имея в распоряжении маленькую лодку. В эту лодку, кроме хозяина, может поместиться только что-то одно — либо волк, либо коза, либо капуста. Нельзя оставлять без присмотра волка с козой, а козу с капустой. Как следует организовать переправу?
Состояния в задачах о перевозке через реку обычно описываются в виде тройки, содержащей множество существ (предметов) на левом берегу, множество существ (предметов) на правом берегу, и берег, у которого находится лодка.
Для решения задач, как и ранее, создается консольный проект. В этом проекте создается модуль depth. В этот модуль следует поместить универсальный решатель, который использует поиск в глубину (листинги 13.1–13.2V = varM::new([]), в версии Visual Prolog 7.4 нужно заменить строкой V = varM{tuple{State*, string*}*}::new([]),
domains
move{State} = (State, string [out]) -> State nondeterm.
predicates
depthSearch: (move{State}, State, State, positive [out])
-> tuple{State*, string*} nondeterm.
open core, list
class facts
minweight : positive := 2^30.
class predicates
depthSearch: (move{State}, State, State, tuple{State*, string*},
positive, positive [out]) -> tuple{State*, string*} nondeterm.
clauses
depthSearch(Move, Start, Goal, minweight) =
tuple(reverse(Path), reverse(Moves)):-
minweight := 2^30,
V = varM::new([]),
foreach tuple(P, Ms) =
depthSearch(Move, Start, Goal, tuple([Start], []), 0, N)
do
if N > minweight then succeed()
elseif N < minweight then
V:value := [tuple(P, Ms)], minweight := N
else V:value := [tuple(P, Ms) | V:value]
end if
end foreach,
tuple(Path, Moves) = getMember_nd(V:value).
depthSearch(_, Goal, Goal, Path, N, N) = Path:- !.
depthSearch(Move, State, Goal, tuple(Path, Moves), C, N) =
depthSearch(Move, NextState, Goal, tuple([NextState | Path],
[Str | Moves]), C + 1, N):-
C < minweight,
NextState = Move(State, Str),
not(isMember(NextState, Path)).
Далее, как обычно, создаются модули для примеров.
open core, console, list, depth
class facts
n : positive := 0.
domains
wstate = tuple(loc BoatLoc, string* LeftSide, string* RightSide).
loc = left; right.
class predicates
wmove : move{wstate}.
unsafestate : (string*) determ.
wmoveFromTo: (positive, string*, string*, string* [out],
string* [out], string* [out]) nondeterm.
subset : (positive, A*, A* [out], A* [out]) nondeterm.
f: (string*) -> string.
clauses
wmoveFromTo(K, FromBef, ToBefore, FromAfter, ToAfter, Boat):-
subset(K, FromBef, Boat, FromAfter),
not(unsafestate(FromAfter)),
ToAfter = sort(append(Boat, ToBefore)).
wmove(tuple(left, L, R), Str) = tuple(right, L1, R1):-
wmoveFromTo(1, L, R, L1, R1, B),
Str = string::format("farmer% moves from left to right", f(B)).
wmove(tuple(right, L, R), Str) = tuple(left, L1, R1):-
K = std::fromTo(0, 1),
wmoveFromTo(K, R, L, R1, L1, B),
Str = string::format("farmer% moves from right to left", f(B)).
f([S]) = string::format(" with %", S):- !.
f(_) = "".
unsafestate(L):-
L = [_, _ | _],
isMember("goat", L).
subset(0, L, [], L):- !.
subset(K, [A | L], [A | L1], L2):-
subset(K - 1, L, L1, L2).
subset(K, [A | L], L1, [A | L2]):-
subset(K, L, L1, L2).
run():-
L = sort(["wolf", "goat", "cabbage"]),
Start = tuple(left, L, []),
Goal = tuple(right, [], L),
tuple([S0 | P], Moves) = depthSearch(wmove, 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().
Следующая программа посвящена решению задачи о рыцарях и оруженосцах (Алкуин, VIII в.). Три рыцаря, каждый со своим оруженосцем, хотят переправиться через реку, с левого берега на правый. В их распоряжении имеется двухместная лодка. Ни один оруженосец не может оставаться где-либо без своего рыцаря в присутствии других рыцарей. Как им переправиться на другой берег?
Имеются варианты задачи для четырех или пяти рыцарей и трехместной лодки. Двухместной лодки в этом случае недостаточно. Если рыцарей шесть и более, то недостаточно и трехместной лодки. Четырехместной лодки достаточно для переправы любого количества рыцарей.
Для того чтобы можно было использовать предикат subset в другом модуле, его объявление нужно перенести в декларацию класса ex1 (листинг 13.4).
predicates
subset : (positive, A*, A* [out], A* [out]) nondeterm.
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().
Предикат 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().
Предикат zip/2 для двух списков одинаковой длины возвращает список, составленный из пар элементов из этих списков с одинаковыми индексами.
Имеется четыре оптимальных решения для задачи о трех миссионерах, 32 оптимальных решения для задачи о четырех миссионерах и 25 оптимальных решений для задачи с пятью миссионерами.
В настоящем параграфе для решения задач применяется поиск в ширину в пространстве состояний. Для ограничения пространства поиска решения не продлеваются в ранее достигнутые состояния. Ведется поиск только одного оптимального решения.
Ниже приводится универсальный решатель задач методом поиска в ширину в пространстве состояний. Он реализуется в модуле breadth (листинги 13.7–13.8V = varM::new([]),
в версии Visual Prolog 7.4 нужно заменить строкой V = varM{State*}::new([]).
predicates
breadthSearch: (depth::move{State}, State, State, positive [out])
-> tuple{State*, string*} determ.
open core, list, depth
domains
t{State} = t(State*, string*, positive).
class predicates
breadthPath: (move{State}, t{State}*, State*, State)
-> t{State} nondeterm.
clauses
breadthSearch(Move, Start, Goal, N) =
tuple(reverse(Path), reverse(Moves)):-
t(Path, Moves, N) =
breadthPath(Move, [t([Start], [], 0)], [Start], Goal),
!.
breadthPath(_, [t([Goal | Path], Ms, N) | _], _, Goal) =
t([Goal | Path], Ms, N):- !.
breadthPath(Move, [t([State|Path], Ms, N) | PL], StateList, Goal)=
breadthPath(Move, append(PL, PL1),
append(V:value, StateList), Goal):-
V = varM::new([]),
PL1 = [t([NextState, State | Path], [Str | Ms], N + 1) ||
NextState = Move(State, Str),
not(isMember(NextState, StateList)),
V:value := [NextState | V:value]].
Применим решатель к задаче о переливаниях. Имеется несколько сосудов, вмещающих целое количество литров воды, и источник воды (река). Требуется отмерить в первом сосуде заданное целое количество литров воды. Можно наливать воду в сосуды из источника, переливать воду из одного сосуда в другой, так чтобы либо первый сосуд становился пустым, либо второй сосуд полным, а также опорожнять сосуды. Обычно считается, что сосуды упорядочены по убыванию вместимости. В этом случае в первом сосуде можно отмерить любое целое количество литров, которое делится на наибольший общий делитель объемов всех сосудов. Потребуем, чтобы нужное количество литров отмерялось в первом, наибольшем сосуде, а остальные сосуды оказывались бы пустыми. Если сосудов два, то можно показать, что задача в этом случае имеет единственное оптимальное решение. Если сосудов более двух, то оптимальных решений более одного. Пространство состояний в этом случае велико, поэтому мы будем искать только одно оптимальное решение с помощью поиска в ширину.
open core, console, list, string, breadth
class facts
volums: (positive*) determ.
clauses
volums([17, 11, 8]). % volums([27, 17, 11, 8, 3]).
class predicates
jmove : depth::move{positive*}.
clauses
jmove(L, Str) = L1:-
memberIndex_nd(A, I, L),
A > 0,
setNth(I, L, 0, L1),
Str = format("Вылить % л из %-го сосуда", A, I + 1).
jmove(L, Str) = L2:-
volums(VL),
memberIndex_nd(A, I, L),
A > 0,
memberIndex_nd(B, J, L), J <> I,
V = nth(J, VL),
B < V,
Vol = math::min(V - B, A),
setNth(I, L, A - Vol, L1),
setNth(J, L1, B + Vol, L2),
Str = format("Перелить % л из %-го сосуда в %-й",
Vol, I + 1, J + 1).
jmove(L, Str) = L1:-
volums(VL),
memberIndex_nd(A, I, L),
V = nth(I, VL),
A < V,
setNth(I, L, V, L1),
Str = format("Налить % л в %-й сосуд", V - A, I + 1).
run():-
Start = [0, 0, 0],
Goal = [4, 0, 0],
tuple([S0 | P], Moves) = breadthSearch(jmove, Start, Goal, _),
V = varM::new(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;
_ = readLine().
Предикат memberIndex_nd/3 недетерминированно возвращает из списка элементы вместе с их индексами. Предикат setNth/4 заменяет элемент списка, стоящий в заданной позиции, заданным элементом. Предикат nth/2 возвращает элемент списка по его индексу. Предикат min/2 возвращает минимум двух чисел.
Используем поиск в ширину для решения задачи о переправе рыцарей и оруженосцев через реку с островом. Она отличается от первоначальной задачи тем, что на реке есть остров, на который можно высаживаться. Известно, что в этом случае решение существует для любого количества пар рыцарей и оруженосцев и двухместной лодки.
Следует перенести указанные ниже объявления в декларацию класса ex2 из его имплементации (листинг 13.10).
domains
knightOrSquire = k(integer); s(integer).
predicates
moveFromTo: (positive, knightOrSquire*, knightOrSquire*,
knightOrSquire* [out], knightOrSquire* [out],
knightOrSquire* [out]) nondeterm.
f: (knightOrSquire*) -> string.
open core, console, list, string, breadth, ex2
domains
kistate = tuple(loc, knightOrSquire*, knightOrSquire*,
knightOrSquire*).
loc = left; right; island.
class predicates
kimove : depth::move{kistate}.
clauses
kimove(tuple(left, L, Isl, R), Str) = tuple(right, L1, Isl, R1):-
moveFromTo(2, L, R, L1, R1, B),
Str = format("% from left to right", f(B)).
kimove(tuple(island, L, Isl, R), Str) = tuple(right, L, Isl1, R1):-
moveFromTo(2, Isl, R, Isl1, R1, B),
Str = format("% from island to right", f(B)).
kimove(tuple(left, L, Isl, R), Str) = tuple(island, L1, Isl1, R):-
moveFromTo(2, L, Isl, L1, Isl1, B),
Str = format("% from left to island", f(B)).
kimove(tuple(right, L, Isl, R), Str) = tuple(left, L1, Isl, R1):-
moveFromTo(1, R, L, R1, L1, B),
Str = format("% from right to left", f(B)).
kimove(tuple(island, L, Isl, R), Str) = tuple(left, L1, Isl1, R):-
moveFromTo(1, Isl, L, Isl1, L1, B),
Str = format("% from island to left", f(B)).
kimove(tuple(right, L, Isl, R), Str) = tuple(island, L, Isl1, R1):-
moveFromTo(1, R, Isl, R1, Isl1, B),
Str = format("% from right to island", f(B)).
run():-
L = sort([s(1), k(1), s(2), k(2), s(3), k(3), s(4), k(4)]),
Start = tuple(left, L, [], []),
Goal = tuple(right, [], [], L),
tuple([S0 | P], Moves) = breadthSearch(kimove, Start, Goal,_),
V = varM::new(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;
_ = readLine().
Наконец, используем поиск в ширину для решения задачи о переправе мисиионеров и каннибалов через реку с островом (конец XIX в.). Если на реке имеется остров, на который можно высаживаться, то переправу можно организовать также для любого количества миссионеров и такого же количества каннибалов и двухместной лодки.
Указанные ниже объявления следует перенести в декларацию класса ex3 из имплементации этого класса (см. листинг 13.12).
predicates
mcmoveFromTo: (positive, positive, positive*, positive*,
positive* [out], positive* [out]) determ.
f: (positive, positive) -> string.
open core, console, list, string, breadth, ex3
domains
mistate = tuple(loc, positive*, positive*, positive*).
loc = left; right; island.
class predicates
mimove : depth::move{mistate}.
clauses
mimove(tuple(left, L, Isl, R), Str) = tuple(right, L1, Isl, R1):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, L, R, L1, R1),
Str = format("% move from left to right", f(Mb, Cb)).
mimove(tuple(left, L, Isl, R), Str) = tuple(island, L1, Isl1, R):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, L, Isl, L1, Isl1),
Str = format("% move from left to island", f(Mb, Cb)).
mimove(tuple(island, L, Isl, R), Str) = tuple(right, L, Isl1, R1):-
Cb = std::downTo(2, 0),
Mb = 2 - Cb,
mcmoveFromTo(Mb, Cb, Isl, R, Isl1, R1),
Str = format("% move from island to right", f(Mb, Cb)).
mimove(tuple(right, L, Isl, R), Str) = tuple(left, L1, Isl, R1):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, R, L, R1, L1),
Str = format("% moves from right to left", f(Mb, Cb)).
mimove(tuple(right, L, Isl, R), Str) = tuple(island, L, Isl1, R1):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, R, Isl, R1, Isl1),
Str = format("% moves from right to island", f(Mb, Cb)).
mimove(tuple(island, L, Isl, R), Str) = tuple(left, L1, Isl1, R):-
Cb = std::fromTo(0, 1),
Mb = 1 - Cb,
mcmoveFromTo(Mb, Cb, Isl, L, Isl1, L1),
Str = format("% moves from island to left", f(Mb, Cb)).
run():-
N = 4,
Start = tuple(left, [N, N], [0, 0], [0, 0]),
Goal = tuple(right, [0, 0], [0, 0], [N, N]),
tuple([S0| P], Moves) = breadthSearch(mimove, Start, Goal,_),
V = varM::new(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;
_ = readLine().
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.