Поиск в пространстве состояний
Настоящая глава посвящена решению задач при помощи графа пространства состояний. Пространство состояний описывается в виде множества состояний — вершин графа, множества переходов от состояния к состоянию — дуг графа, множества начальных состояний и множества конечных состояний. Решение задачи представляется в виде пути на графе пространства состояний, соединяющего начальное состояние с конечным.
Если пространство состояний задачи невелико, то будут находиться все оптимальные решения с помощью поиска в глубину. В задачах с большим пространством состояний будет вычисляться только одно оптимальное решение посредством поиска в ширину.
К задачам применяются универсальные решатели. Состояния в разных задачах могут принадлежать различным доменам. Для запоминания наилучших среди найденных решений используется "изменяемая переменная" 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