Парсеры
11.3. Поиск наибольшего общего унификатора
В данном параграфе реализуется алгоритм поиска наибольшего общего унификатора двух термов (или атомарных формул). Формулы подаются программе в виде двух строк. Парсер преобразует их в термы специального вида, которые и поступают на вход алгоритма.
Грамматика исходных формул описывается следующим образом:
term ::= var | const | fun [(] termlist [)] termlist ::= term | term [,] termlist
Переменные пишутся с прописной буквы, а константы, предикатные и функциональные символы — со строчной, например,
.
Алгоритм поиска наибольшего общего унификатора термов
и
с помощью двух стеков
и
описан в [9]. Он заключается в следующем. Вначале в
помещается пара термов
, стек
пуст. Алгоритм начинается с шага 1.
- Если стек
пуст, то алгоритм завершает свою работу и унификатор полагается равным
, иначе совершается переход к шагу 2. -
Из
извлекается пара термов
. Возможны случаи:- если
— переменная, а
— терм, не содержащий
, то в
и
выполняется замена
на
, из
удаляются пары совпадающих термов, в
добавляется пара термов
и выполняется переход к шагу 1; - если
— переменная, а
— терм, не содержащий
, то в
и
выполняется замена
на
, из
удаляются пары совпадающих термов, в
добавляется
и выполняется переход к шагу 1; - если
и
— пара совпадающих переменных или констант, то выполняется переход к шагу 1; - если
и
, то в
добавляются пары термов
и выполняется переход к шагу 1; - в остальных случаях алгоритм завершает работу — термы не унифицируемы.
- если
open core, console, string
domains
term = var(string Var); c(string Const); f(string Name, term*).
% парсер
class predicates
scan: (string) -> string*.
parser: (string*, term [out], string* [out]) determ.
parser: (string*, term* [out]) -> string* determ.
checkName: (string) determ.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
parser([S, "(" | L], f(S, TermList), Rest):- !,
checkName(S),
[")" | Rest] = parser(L, TermList).
parser([S | L], var(S), L):-
isName(S),
Ch = frontChar(S),
Ch = charUpper(Ch),
!.
parser([S | L], c(S), L):-
(_ = tryToTerm(unsigned, S); _ = tryToTerm(real, S);
checkName(S)),
!.
parser(["," | L], TermList) = parser(L, TermList):- !.
parser(L, [Term | TermList]) = parser(Rest, TermList):-
parser(L, Term, Rest),
!.
parser(L, []) = L.
checkName(S):-
Ch = charToString(frontChar(S)),
hasAlpha(Ch),
isLowerCase(Ch).
% поиск наибольшего общего унификатора
class predicates
unify: (term, term) -> term* determ.
unif: (term*, term*) -> term* determ.
subterm: (term, term) determ.
replace: (term*, term, term) -> term*.
delete: (term*) -> term*.
put: (term*, term*, term*) -> term* determ.
clauses
unify(X, Y) = unif([X, Y], []).
unif([var(X), var(X) | L], Subst) = unif(L, Subst):- !.
unif([c(X), c(X) | L], Subst) = unif(L, Subst):- !.
unif([var(X), Y | L], Subst) = unif(replace(L, var(X), Y),
[var(X), Y | delete(replace(Subst, var(X), Y))]):- !,
not(subterm(var(X), Y)).
unif([Y, var(X) | L], Subst) = unif([var(X), Y | L], Subst):- !.
unif([f(N, TL1), f(N, TL2) | L], Subst) = unif(L1, Subst):-
L1 = put(TL1, TL2, L).
unif([], Subst) = Subst.
subterm(X, X):- !.
subterm(X, f(_, L)):-
list::exists(L, {(H):- subterm(X, H)}).
replace([X | L], X, Y) = [Y | replace(L, X, Y)]:- !.
replace([H | L], X, Y) = [H | replace(L, X, Y)].
replace([], _, _) = [].
delete([X, X | L]) = delete(L):- !.
delete([X, Y | L]) = [X, Y | delete(L)]:- !.
delete(_) = [].
put([X | L1], [Y | L2], L) = put(L1, L2, [X, Y | L]).
put([], [], L) = L.
% преобразование в строку
class predicates
s: (term) -> string.
toStr: (term*) -> string*.
clauses
s(var(X)) = X:- !.
s(c(X)) = X:- !.
s(f(N, L)) = format("%(%)", N,
concatWithDelimiter(list::map(L, {(T) = s(T)}), ", ")).
toStr([X, Y | L]) = [format("% = %", s(X), s(Y)) | toStr(L)]:- !.
toStr(_) = [].
run():-
S1 = "p(X, f(b, Y), g(a, a), k(h(1, 2, 3)), U)",
S2 = "p(a, f(b, a), g(a, Y), k(Z), V)",
parser(scan(S1), Term1, _),
parser(scan(S2), Term2, _),
Subst = unify(Term1, Term2),
writef("%\n%\n\n%", s(Term1), s(Term2),
concatWithDelimiter(toStr(Subst), ", ")),
fail;
_ = readLine().
Пример
11.3.
Разбор термов. Реализация алгоритма поиска НОУ
Предикат charToString преобразует символ (char) в строку (string), предикат isLowerCase истинен, если все символы строки имеют нижний регистр, предикат hasAlpha истинен, если строка состоит только из букв, предикат exists проверяет, имеется ли в списке элемент, удовлетворяющий заданному условию.
Упражнение 2. Реализуйте в программе, приведенной в листинге 11.3, возможность использования анонимных переменных (напомним, что они унифицируются с любыми термами, но не принимают значений).
Упражнения
- Напишите программу, которая выполняет перевод простых английских предложений на немецкий язык.
- Напишите программу, которая разбирает и вычисляет выражения с комплексными числами.
- Напишите программу, которая разбирает и упрощает тригонометрические выражения.
- Напишите программу, которая выполняет действия над многочленами — сложение, умножение на число, умножение, деление с остатком.
- Напишите программу, которая строит польскую запись арифметических выражений.
- Требуется так расставить между шестью девятками знаки сложения, вычитания умножения и деления, чтобы в результате вычисления получилось число 100.
- Требуется получить число 24 из трех пятерок и единицы, расставив между ними знаки сложения, вычитания умножения, деления и скобки.
- Напишите программу, которая выполняет разбор и линеаризует списки вида "[0, [1, [2, 3, [4]]], 5]". Список подается на вход в виде строки. Линейный список также выдается в виде строки.