Опубликован: 12.02.2014 | Доступ: свободный | Студентов: 819 / 236 | Длительность: 11:22:00
Специальности: Программист
Лекция 12:

Язык запросов

12.4. Синтаксический разбор запросов

Настоящий параграф посвящен разработке языка запросов к базе данных, близкого к естественному языку. В запросах представлены операции объединения, пересечения и разности множеств (найти сыновей Петра и дочерей мужчин), композиции отношений (найти сыновей предков Петра) и отрицания (найти сыновей Петра, но не сыновей Марии). Объединение представляют союзы "и" и "или", пересечение — знак запятой "," (поэтому его нельзя использовать просто как знак пунктуации), а отрицание — частица "не". Все запросы формулируются через набор основных отношений, которыми являются бинарные отношения "родитель", "ребенок", "отец", "мать", "муж", "жена", "сын", "дочь", "сестра", "брат", "предок" и "потомок", а также унарные отношения "мужчина" и "женщина". Например, найти тетушек Петра можно с помощью запроса "найти сестер родителей Петра". Без аргумента в запросе могут быть только унарные отношения. Например, нельзя попросить "найти отцов", но можно "найти отцов мужчин и отцов женщин".

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

query    ::=  conj  conjs
conj    ::=  elem  elems
elem    ::=  rel  elem  |  [не]  rel  elem  |  name  surname  |  name  |  unrel
elems    ::=  consign  elem  elems  |  none
conjs    ::=  dizsign  conj  conjs  |  none
consign ::=  [,]
dizsign    ::=  [и]  |  [или]
unrel    ::=  [мужчина]  |  [женщина]  % и изменения по падежам и числам
rel    ::=  [родитель]  |  [предок]  | … % и изменения по падежам и числам

Запросы могут быть вида:

Найти братьев мужчин и сына Петра;

Найти предков потомков предков дочерей Петра Иванова или Марию.

Найти предков Петра, но не сыновей отца отца Петра.

Программа удаляет из запроса слова, не значимые при вычислении ответа на запрос, т. е. "игнорируемые" слова. Например, из приведенных выше запросов удаляются слова "найти" и "но".

Имена и фамилии людей, а также названия отношений изменяются по падежам и числам. Поэтому при обработке запроса выполняется операция нормализации таких слов. Для отношений проверяется, является ли неизменяемая часть слова префиксом названия отношения в запросе. Например, если слово в запросе имеет префикс "сест", то оно распознается как название отношения "сестра". Отношение "ребенок" могут представлять слова как с префиксом "ребен", так и с префиксом "дет" (см. определение отношения rel в программе).

Имена обрабатываются следующим образом. Сначала проверяется, имеется ли такое имя в базе данных (с учетом регистра). Если нет, то отнимается один символ с конца слова и проверяется, является ли полученное слово префиксом некоторого имени. Затем, при необходимости, отнимается еще один символ, и т. д. Фамилии распознаются аналогичным образом, в паре с именами.

Например, запрос

Найти сестер Анны и сыновей Петра Иванова

преобразуется в терм

diz(rel("сестра", n("Анна")), rel("сын", ns("Петр", "Иванов")))

Ниже приведен класс parser с интерфейсом parser (листинги 12.2812.30).

domains
    term = diz(term, term); con(term, term); neg(term);
        rel(string, term); unrel(string); n(string); ns(string, string).

predicates
    parse: (string) -> term.
Пример 12.28. Интерфейс parser
constructors
    new: (dbrel).
Пример 12.29. Декларация класса parser
facts
    db: dbrel.

clauses
    new(Db):-
        db := Db.

    parse(Str) = Term:-
        L = scan(string::toLowerCase(Str)),
        L1 = list::filter(L, {(S):- not(list::isMember(S, ignor))}),
        parser(query, L1, Term, Rest),
        !,
        write(Rest), nl.
    parse(_) = n("").

predicates
    scan: (string) -> string*.
clauses
    scan(Str) = [Tok | scan(RestStr)]:-
        string::frontToken(Str, Tok, RestStr),
        !.
    scan(_) = [].

facts
    ignor : string* := ["найти", "вычислить", "но", "которые", 
        "являются", "а", "также"].

domains
    nt = query; conj; conjs; elem; elems.

predicates
    parser: (nt, string*, term [out], string* [out]) determ.
    parser: (nt, string*, term, term [out], string* [out]).
clauses
    parser(query, L, Term, Rest):-
        parser(conj, L, Term1, L1),
        parser(conjs, L1, Term1, Term, Rest).
    parser(conj, L, Term, Rest):-
        parser(elem, L, Term1, L1),
        parser(elems, L1, Term1, Term, Rest).
    parser(elem, ["не" | L], neg(Term), Rest):-
        parser(elem, L, Term, Rest),
        !.
    parser(elem, [Rel | L], unrel(Rel1), L):-
        Rel1 = norm("ur", Rel),
        !.
    parser(elem, [Rel | L], rel(Rel1, Term), Rest):-
        Rel1 = norm("r", Rel),
        !,
    parser(elem, L, Term, Rest).
    parser(elem, [Name, Surname | L], ns(Name1, Surname1), L):-
        norm(Name, Surname, Name1, Surname1),
        !.
    parser(elem, [Name | L], n(Name1), L):-
        Name1 = norm("n", Name).

    parser(conjs, [S | L], Term1, Term, Rest):-
        dizsign(S),
        parser(conj, L, Term2, L1),
        !,
        parser(conjs, L1, diz(Term1, Term2), Term, Rest).
    parser(elems, [S | L], Term1, Term, Rest):-
        consign(S),
        parser(elem, L, Term2, L1),
        !,
        parser(elems, L1, con(Term1, Term2), Term, Rest).
    parser(_, L, Term, Term, L).

facts
    consign: (string).
    dizsign: (string).
    rel: (string, string).
    urel: (string, string).
clauses
    consign(",").

    dizsign("и").
    dizsign("или").

    rel("родитель", "родител").
    rel("ребенок", "ребен").
    rel("ребенок", "дет").
    rel("отец", "отц").
    rel("отец", "отец").
    rel("мать", "мат").
    rel("муж", "муж").
    rel("жена", "жен").
    rel("сын", "сын").
    rel("дочь", "доч").
    rel("сестра", "сест").
    rel("брат", "брат").
    rel("предок", "пред").
    rel("потомок", "потом").

    urel(dbrel::male, "мужчин").
    urel(dbrel::female, "женщин").

predicates
    norm: (string, string) -> string determ.
    norm: (string, string, string [out], string [out]) determ.
    getPrefix_nd: (string) -> string nondeterm.
clauses
    getPrefix_nd(S) = S.
    getPrefix_nd(S) = getPrefix_nd(Prefix):-
        L = string::length(S), L > 2,
        string::front(S, L - 1, Prefix, _).

    norm("r", S) = NormS:-
        rel(NormS, Sub),
        string::hasPrefix(S, Sub, _),
        !.
    norm("ur", S) = NormS:-
        urel(NormS, Sub),
        string::hasPrefix(S, Sub, _),
        !.
    norm("n", S) = Name:-
        db:person_nd(_, Name, _, _, _, _),
        Name1 = string::toLowerCase(Name),
        S1 = getPrefix_nd(S), 
        string::hasPrefix(Name1, S1, _),
        !.

    norm(N, S, Name, Surname):-
        db:person_nd(_, Name, Surname, _, _, _),
        Name1 = string::toLowerCase(Name),
        SName1 = string::toLowerCase(Surname),
        N1 = getPrefix_nd(N), 
        string::hasPrefix(Name1, N1, _),
        S1 = getPrefix_nd(S), 
        string::hasPrefix(SName1, S1, _),
        !.
Пример 12.30. Имплементация класса parser

Предикат front возвращает префикс строки, состоящий из заданного количества символов, и остаток строки. Предикат hasPrefix проверяет, является ли заданная подстрока префиксом строки и возвращает остаток строки.

12.5. Вычисление ответов на запросы

В настоящем параграфе реализуется процедура поиска ответов на вопросы. Все вычисления проводятся на множестве идентификаторов. Результом вычисления запроса является список идентификаторов. Из списков удаляются повторяющиеся элементы. К спискам применяются операции объединения, пересечения и разности.

Для реализации вычислений создается класс calculation с интерфейсом calculation (листинги 12.3112.33).

predicates
    calc: (parser::term) -> unsigned* determ.
Пример 12.31. Интерфейс calculation
constructors
    new: (relation).
Пример 12.32. Декларация класса calculation
    open core, parser, list

facts
    rel: relation.

clauses
    new(R):-
        rel := R.

predicates
    n: (A*) -> A*.
    calc1: (term) -> unsigned nondeterm.
clauses
    n(L) = removeDuplicates(L).

    calc1(X) = getMember_nd(calc(X)).

    calc(diz(X, Y)) = union(calc(X), calc(Y)).
    calc(con(X, Y)) = intersection(calc(X), calc(Y)).
    calc(neg(X)) = difference(L, calc(X)):-
        L = [I || rel:db:person_nd(I, _, _, _, _, _)].
    calc(parser::n(N)) = [I || rel:db:person_nd(I, N, _, _, _, _)].
    calc(ns(N, S)) = [I || rel:db:person_nd(I, N, S, _, _, _)].
    calc(unrel(Sex)) = [I || rel:db:person_nd(I, _, _, Sex, _, _)].
    calc(rel("родитель", X)) = n([I || rel:parent(I, calc1(X))]).
    calc(rel("ребенок", X)) = n([I || rel:parent(calc1(X), I)]).
    calc(rel("отец", X)) = n([I || rel:father(I, calc1(X))]).
    calc(rel("мать", X)) = n([I || rel:mother(I, calc1(X))]).
    calc(rel("муж", X)) = n([I || rel:husband(I, calc1(X))]).
    calc(rel("жена", X)) = n([I || rel:husband(calc1(X), I)]).
    calc(rel("сын", X)) = n([I || rel:son(I, calc1(X))]).
    calc(rel("дочь", X)) = n([I || rel:daughter(I, calc1(X))]).
    calc(rel("сестра", X)) = n([I || rel:sister(I, calc1(X))]).
    calc(rel("брат", X)) = n([I || rel:brother(I, calc1(X))]).
    calc(rel("предок", X)) = n([I || rel:ancestor(I, calc1(X))]).
    calc(rel("потомок", X)) = n([I || rel:ancestor(calc1(X), I)]).
Пример 12.33. Имплементация класса calculation

По списку идентификаторов восстанавливаются имена и фамилии людей, которые и выдаются в качестве ответа на запрос.

В упражнениях 4 – 8 (см. ниже) требуется создать базу данных, а также придумать и реализовать язык запросов к ней, близкий к естественному языку.

Упражнения

  1. Добавьте в базу данных сведения о годах жизни людей. Добавьте запросы, связанные с годами жизни.
  2. Добавьте в базу данных и в язык запросов отношения свойства.
  3. Добавьте в базу данных и в язык запросов сведения о роде занятий и о месте жительства людей.
  4. База данных "Династия" (Романовых, Рюриковичей или др.).
  5. База данных "География России", описывающая взаимоотношения между объектами некоторой области.
  6. База данных "Биология", описывающая взаимоотношения между растениями некоторого семейства.
  7. База данных "Естественные языки", описывающая взаимоотношения между языками в некоторой группе языков.
  8. База данных "Языки программирования", описывающая взаимоотношения между языками в некоторой парадигме.
Жаныл Айкын
Жаныл Айкын
Rustam Inatov
Rustam Inatov

Доброго времени суток, подскажите пожалуйста, visual prolog examples, pie, vip7.5 - это все, где я могу скачать? (в смысле) может быть на сайте есть какой-то архив? Увы я не нашел его.

Подскажите, пожалуйста.

С уважением, Рустам.