Поиск в пространстве состояний
Настоящая глава посвящена решению задач при помощи графа пространства состояний. Пространство состояний описывается в виде множества состояний — вершин графа, множества переходов от состояния к состоянию — дуг графа, множества начальных состояний и множества конечных состояний. Решение задачи представляется в виде пути на графе пространства состояний, соединяющего начальное состояние с конечным.
Если пространство состояний задачи невелико, то будут находиться все оптимальные решения с помощью поиска в глубину. В задачах с большим пространством состояний будет вычисляться только одно оптимальное решение посредством поиска в ширину.
К задачам применяются универсальные решатели. Состояния в разных задачах могут принадлежать различным доменам. Для запоминания наилучших среди найденных решений используется "изменяемая переменная" varM. Компилятор сам находит нужные типы.
13.1. Поиск в глубину в пространстве состояний
В настоящем параграфе поиск в глубину в пространстве состояний применяется для решения известных задач. Сначала решатель применяется к задаче о волке, козе и капусте. Для решения других задач в программу добавляются лишь описания состояний и переходов между ними.
Задача о волке, козе и капусте заключается в следующем (Алкуин, XIII в.). Хозяин с волком, козой и грудой кочанов капусты должен перебраться через реку с левого берега на правый, имея в распоряжении маленькую лодку. В эту лодку, кроме хозяина, может поместиться только что-то одно — либо волк, либо коза, либо капуста. Нельзя оставлять без присмотра волка с козой, а козу с капустой. Как следует организовать переправу?
Состояния в задачах о перевозке через реку обычно описываются в виде тройки, содержащей множество существ (предметов) на левом берегу, множество существ (предметов) на правом берегу, и берег, у которого находится лодка.
Для решения задач, как и ранее, создается консольный проект. В этом проекте создается модуль depth. В этот модуль следует поместить универсальный решатель, который использует поиск в глубину (листинги 13.1–13.2)1В листинге 13.2 строку из версии Visual Prolog 7.5 V = 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.
Пример
13.1.
Декларация класса depth
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)).
Пример
13.2.
Имплементация класса depth
Далее, как обычно, создаются модули для примеров.
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().
Пример
13.3.
Задача о волке, козе и капусте. Модуль ex1