Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Волченков Логическое программирование язык пролог 2015

.pdf
Скачиваний:
11
Добавлен:
12.11.2022
Размер:
4.14 Mб
Скачать

где

i

 

i

 

и существует правило:

i → i.

 

 

 

 

 

Порождением называется отношение

 

 

 

n,

 

n = 1, 2, ...

если существует последовательность отношений1 n.

Языком, порождаемым грамматикой G, называется следующее множество:

L(G) = { S }

(множество терминальных цепочек – цепочек, состоящих только из символов терминального словаря VT).

Грамматики и языки подразделяют на типы. В практических задачах наиболее часто используются грамматики и языки следующих трех типов.

Типы грамматик:

Если нетерминальный символ в левой части какого-либо правила грамматики находится в окружении других символов (терминальных и/или нетерминальных), то такая грамма-

тика называется контекстно-зависимой.

Грамматика называется контекстно-свободной, если левая часть каждого правила вывода этой грамматики – это цепочка, состоящая из единственного нетерминального символа.

Контекстно-свободная грамматика называется автоматной,

если каждое правило вывода этой грамматики имеет следующий вид:

А аВ или А а или А ,

где A, B – нетерминальные символы, a – терминальный символ, λ – пустая цепочка.

Типы языков:

Язык называется автоматным, если для его порождения можно построить автоматную грамматику. (Разумеется, для порождения этого языка можно построить грамматики и других типов.)

71

Язык называется контекстно-свободным, если для его порождения можно построить контекстно-свободную грамматику, но нельзя построить автоматную грамматику.

Язык называется контекстно-зависимым, если для его порождения нельзя построить контекстно-свободной грамматики.

2. Простейший анализатор для автоматного языка

Пример 5.1. Рассмотрим грамматические правила, с помощью которых строятся языковые конструкции вида:

«кот», «большой черный кот», «рыжий пес», «огромный рыжий лохматый пес».

Правила эти такие:

Г_С → СУЩ | ПР Г_С СУЩ → кот | пес | ...

ПР → большой | огромный | черный | лохматый | рыжий |

Нетрудно показать, что для рассмотренного «языка» можно построить автоматную грамматику. Можете сделать это самостоятельно.

Здесь Г_С означает «группу существительного» («noun group» в англоязычной литературе – отсюда и «калька»), СУЩ, ПР – существительное и прилагательное.

Отметим, что среди представленных выше правил есть рекурсивное: Г_С → ПР Г_С. Оно позволяет порождать бесчисленное множество сколь угодно длинных «групп существительного».

Если исходные конструкции представить в виде списков Пролога, например, [большой, черный, кот], можно легко организовать их анализ с помощью очень простого приведенного ниже определения на Прологе (код 5.1).

Код 5.1

test1([большой, чеpный, кот]). test2([кот]).

test3([большой, pыжий, лохматый, пес]).

an(gs(s(H)), [H]). an(gs(p(H), X), [H|T]):-

an(X, T).

72

Отметим, что это определение представляет собой интерпретируемый код, то есть может быть непосредственно загружено в базу данных Пролога и проинтерпретировано (тексты в данной лекции с помощью системы LPA Win Prolog 4.200).

Задав целевые утверждения:

?-test1(L), an(Res, L). ?-test2(L), an(Res, L). ?-test3(L), an(Res, L).

в качестве значения переменной Res мы получим следующие структуры данных:

Res = gs(p(большой), gs(p(черный), gs(s(кот))));

Res = gs(s(кот));

Res = gs(p(большой), gs(p(рыжий), gs(p(лохматый), gs(s(пес))))).

3. «Наивный» анализатор для КС-грамматики

Пример 5.2. Усложним задачу. Если в предыдущем примере анализатор работал как детерминированный автомат, то для произвольных контекстно-свободных грамматик (КС-грамматик) мы вынуждены перейти к недетерминированным методам.

В частности, если необходимо анализировать более сложные фразы, состоящие как из групп существительного, так и из глагольных групп, например:

«большой кот вскочил в переполненный московский трамвай»,

приходится «рассекать» эту фразу на части произвольным образом и каждый раз проверять, соответствуют ли эти части интересующим нас грамматическим конструкциям.

Прежде всего, зададим грамматику, порождающую фразы из выбранного нами класса:

ФР → Г_С Г_Г Г_С → СУЩ | ПР Г_С

Г_Г → ГЛ | ГЛ ПД Г_С СУЩ → кот | пес | трамвай | тротуар | ...

ПР → большой | огромный | черный | рыжий | лохматый | московский | переполненный | ...

ГЛ → вскочил | выскочил | прогуливался | ...

ПД → в | на | ...

73

Первое, что напрашивается при попытке написания синтаксического анализатора на Прологе – это использование предиката append/3 для разбиения списка, представляющего исходную цепочку, на части:

an_ph(ph(X, Y), L):- append(L1, L2, L), an_gs(X, L1), an_gg(Y, L2).

...

Такая «наивная» попытка построения анализатора с использованием предиката append/3 оказывается весьма неудачной, так как указанный предикат работает неэффективно, особенно тогда, когда цепочку в соответствии с грамматикой необходимо разбивать более чем на две части.

4. Встроенный в Пролог механизм DCG

Как выяснилось, можно обойтись без неэффективного предиката append/3, воспользовавшись понятием разностного списка или (что эквивалентно) введя дополнительный аргумент в предикаты анализатора.

Разностный список – это структура инфиксного типа с именем \ и двумя компонентами: списком [A1, …, An | T] и списком T:

[A1, …, An | T] \ T.

Такая структура эквивалентна обычному списку, содержащему n элементов: [A1, …, An]. Но в записи разностного списка есть неозначенная переменная T, значением которой может быть любой список. Можно привести аналогию: любое число, например 5, равно разности, содержащей переменную: (5 + X) – X.

Использование понятия «разностный список» позволяет существенно повысить эффективность работы многих предикатов обработки списков, например предиката append/3 (конкатенации или «склеивания»). Вместо определения

append([], L, L).

append([H|T], L, [H|R]) :- append(T, L, R).

можно воспользоваться гораздо более эффективным определением:

74

append_dl(X\Y, Y\Z, X\Z).

Последнее определение позволяет «склеивать» списки (длина их может быть весьма большой) без многочисленных рекурсивных вызовов, – а только на уровне сопоставления структур!

A A A A A B B B B B C C C C

C

 

 

 

 

X \ Y

 

 

 

B B B B B C C C C

C

 

 

 

 

Y \ Z

 

 

C C C C C

 

 

 

 

Z

Рис. 5.1. Демонстрация конкатенации разностных списков

Пример 5.3. Пусть L1 = [a, b, c]; L2 = [d, e, f].

Необходимо найти конкатенацию этих списков.

Вызов ?- append([a, b, c], [d, e, f], Z).

приводит к необходимости трёх рекурсивных вызовов, в то время как вызов

?- append_dl([a, b, c|X] \ X, [d, e, f] \ [], Z).

дает результат Z = [a, b, c, d, e, f] \ [] за единственный шаг логиче-

ского вывода.

Эксперимент в системе Win Prolog LPA 4.200:

:- op(200, xfx, '\'). append_dl(X\Y, Y\Z, X\Z).

Консоль:

# 0.000 seconds to consult untitled [c:\prolog\lpa2002\] | ?- append_dl([a, b, c|X]\X, [d,e,f]\[], Z).

X = [d,e,f] ,

Z = [a,b,c,d,e,f] \ []

Пример 5.4. Рассмотрим программу (код 5.2), реализующую анализатор для приведенного выше примера контекстно-свободной грамматики, порождающей фразы типа

«большой кот вскочил в переполненный московский трамвай»,

с использованием разностных списков вместо предиката append/3.

75

Код 5.2

% Разборщик с использованием разностных списков

:- op(200, xfx, '\').

test1([большой, чеpный, кот, вскочил, в, пеpеполненный,

московский, тpамвай]). test2([кот,пpогуливался]).

test3([большой, pыжий, лохматый, пес, выскочил, на, тpотуаp]).

s_list([кот,пес,тpамвай,тpотуаp]).

p_list([большой, лохматый, московский, пеpеполненный, pыжий, чеpный]).

g_list([вскочил, выскочил, пpогуливался]). pd_list([в, на]).

an_ph(ph(X, Y), L\M) :-

an_gs(X, L\M1),

an_gg(Y, M1\M).

an_gs(gs(X), L\M) :-

an_s(X, L\M).

an_gs(gs(X,Y), L\M) :-

an_p(X, L\M1),

an_gs(Y, M1\M).

an_gg(gg(X), L\M) :-

an_g(X, L\M).

an_gg(gg(X,Y,Z), L\M) :-

an_g(X, L\M1),

an_pd(Y, M1\M2),

an_gs(Z, M2\M).

an_s(s(X), [X|M]\M) :- s_list(L), member(X,L).

76

an_p(p(X), [X|M]\M) :- p_list(L), member(X,L).

an_g(g(X), [X|M]\M) :- g_list(L), member(X,L).

an_pd(pd(X), [X|M]\M) :- pd_list(L), member(X,L).

%Целевые утверждения:

%?-test1(L), an_ph(Res, L\[]).

%?-test2(L), an_ph(Res, L\[]).

%?-test3(L), an_ph(Res, L\[]).

Консоль (для 1-го теста):

# 0.000 seconds to consult pars_dl.pl [c:\prolog\lpa2002\] | ?- test1(L), an_ph(Res, L\[]).

L = [большой, чеpный, кот, вскочил, в, пеpеполненный, московский, тpамвай] ,

Res = ph(gs(p(большой), gs(p(чеpный), gs(s(кот)))),

gg(g(вскочил), pd(в), gs(p(пеpеполненный), gs(p(московский), gs(s(тpамвай))))))

| ?-

При синтаксическом анализе разностный список можно использовать неявно: он фактически появляется при введении дополнительного аргумента в предикаты анализатора.

Этот аргумент после успешного срабатывания данного предиката получает значение, равное оставшейся части входной цепочки после отделения от нее анализируемой конструкции. Так, приведенный выше предикат an_ph/2 превращается в предикат an_ph/3:

an_ph(ph(X, Y), L, M):-

an_gs(X, L, M1),

an_gg(Y, M1, M).

Рассмотрим более общий абстрактный пример.

77

Пусть грамматическое правило содержит N нетерминальных символов в правой части: A → B1 B2 ... BN

Соответствующее правило на Прологе будет иметь вид

an_a(a(X1, X2, ..., XN), L, M):- an_b1(X1, L, M1), an_b2(X2, M1, M2),

...

an_bN(XN, MN-1, M).

Обычно, M = []. После успешного срабатывания предиката an_b1 от исходной цепочки L останется остаток M1, который будет исходной цепочкой для предиката an_b2 и т.д.

В Прологе существует встроенный механизм, обеспечивающий компактную форму записи приведенных выше правил.

Аргументы предикатов, соответствующие входной цепочке и остаткам (L, M, M1, M2, …, MN-1), не записываются, знак :- заменяется на знак -->. Вышеприведенное правило приобретает вид

an_a(a(X1, X2, ..., XN)) --> an_b1(X1), an_b2(X2),

...

an_bN(XN).

При трансляции этого правила в Пролог получится

an_a(a(X1, X2, ..., XN), A, B) :-

an_b1(X1, A, C1),

an_b2(X2, C1, C2),

...

an_bN(XN, CN-1, B).

Обратим внимание на то, что при трансляции переменные A, B, Ci добавляются к остальным аргументам предикатов всегда «справа» – после уже имеющихся.

Терминальным символам в правых частях грамматических правил в нашей нотации соответствуют списки, элементы которых – указанные терминальные символы.

Например, правилу грамматики

78

S → a S b c,

где S – нетерминальный символ; a, b, c – терминальные символы, соответствует следующее правило в представляемой нотации:

an_s(s(a, X, b, c)) --> [a], an_s(X), [b, c].

При трансляции этого правила в Пролог (как подсказывает здравый смысл) должно получиться следующее правило:

an_s(s(a, X, b, c), L, M) :- L = [a|M1],

an_s(X, M1, M2), M2 = [b, c|M].

Очевидно, при трансляции запись этого правила «оптимизируется»:

an_s(s(a, X, b, c), [a|M1], M) :- an_s(X, M1, [b, c|M]).

Проверка показывает, что это соответствует действительности. Если в процессе анализа необходимо реализовать какие-либо

действия, которые должны быть описаны в виде подцелей на Прологе, в правых частях правил эти подцели заключаются в фигурные скобки.

Указанные выше соглашения, относящиеся к «новой» нотации правил, представляют так называемый механизм DCG Definite Clause Grammar; с его помощью удобно определять эффективные нисходящие синтаксические анализаторы по заданным порождающим грамматикам, которые, как это будет видно из дальнейшего, не обязательно должны быть контекстно-свободными!

Пример 5.5. Представим программу (код 5.3) в нотации DCG, реализующую анализатор для приведенного выше Примера 5.4 контекстно-свободной грамматики, порождающей фразы типа:

«большой кот вскочил в переполненный московский трамвай».

79

Код 5.3

test1([большой, чеpный, кот, вскочил, в, пеpеполненный, московский, тpамвай]).

test2([кот, пpогуливался]).

test3([большой, pыжий, лохматый, пес, выскочил, на, тpотуаp]).

s_list([кот, пес, тpамвай, тpотуаp]).

p_list([большой, лохматый, московский, пеpеполненный, pыжий, чеpный]).

g_list([вскочил, выскочил, пpогуливался]). pd_list([в, на]).

an_ph(ph(X, Y)) --> an_gs(X), an_gg(Y).

an_gs(gs(X)) --> an_s(X).

an_gs(gs(X,Y)) --> an_p(X), an_gs(Y).

an_gg(gg(X)) --> an_g(X).

an_gg(gg(X,Y,Z)) --> an_g(X), an_pd(Y), an_gs(Z).

an_s(s(X)) --> an_p(p(X)) --> an_g(g(X)) --> an_pd(pd(X)) -->

[X], {s_list(L), member(X,L)}. [X], {p_list(L), member(X,L)}. [X], {g_list(L), member(X,L)}. [X], {pd_list(L), member(X,L)}.

80

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]