Как использовать DCG в Прологе - PullRequest
1 голос
/ 19 февраля 2020

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

courses(
    [
     ('MATH2221',
      [
       201000001,
       201000002
      ]
     ),

     ('MATH2251',
      [
       201000002,
       201000003
      ]
     ),

     ('COMP2231',
      [
       201000003,
       201000001
      ]
     )
    ]
).

Текстовый файл, который я анализирую по взглядам как это:

MATH2221
       201000001
       201000002

MATH2251
       201000002
       201000003

COMP2231
       201000003
       201000001

Я читал в Интернете, что использование DCG, вероятно, является лучшим способом go об этом, поскольку каждый студент начинает с вкладки, поэтому значение ascii '9' и затем курсы разделяются на 2 nl персонажи. Я действительно потерял пролог и собираюсь опубликовать только то, что у меня сейчас работает, потому что все остальное - беспорядок. У кого-нибудь есть какой-либо совет или он может хотя бы помочь мне понять, что такое DCG?

:- debug.
:- [library(dcg/basics)].

load:-
    open('courses.txt',read,Stream),
         read,
         close(Stream).

read:-
    open('courses.txt',read,In),
    repeat,
    read_line_to_codes(In,X),write(X), nl,
    (X=end_of_file,!,
    nl); fail.

1 Ответ

2 голосов
/ 19 февраля 2020

Хотя идея того, что вы спрашиваете, проста, а перевод в DCG кажется относительно простым, на практике требуется опыт и умение, чтобы знать, как это сделать правильно и эффективно.

Следующее работает с SWI- Пролог (с резьбой, 64 бита, версия 8.1.21) на Windows 10

:- [library(dcg/basics)].

courses([Course|Courses]) -->
    course(Course),
    courses(Courses), !.
courses([]) --> [].

course(course(Course,Students)) -->
    string_without("\n", Course_codes),
    { string_codes(Course,Course_codes ) },
    "\n",
    students(Students),
    (
        empty_line
    ;
        []
    ).

students([Student|Students]) -->
    student(Student),
    students(Students).
students([]) --> [].

student(Student) -->
    "\t",
    (
        (
            string_without("\n",Student_codes),
            { string_codes(Student,Student_codes) },
            "\n"
        )
    ;
        remainder(Student_codes),
        { string_codes(Student,Student_codes) }
    ).

empty_line --> "\n".

load_courses :-
    Input = "\c
MATH2221\n\c
    \t201000001\n\c
    \t201000002\n\c
    \n\c
MATH2251\n\c
    \t201000002\n\c
    \t201000003\n\c
    \n\c
COMP2231\n\c
    \t201000003\n\c
    \t201000001\c
",
    string_codes(Input,Codes),
    DCG = courses(Courses),
    phrase(DCG,Codes,Rest),
    assertion( Rest == [] ),
    format('Courses: ~n',[]),
    print_term(Courses,[]).

Пример выполнения:

?- load_courses.
Courses: 
[ course("MATH2221",["201000001","201000002"]),
  course("MATH2251",["201000002","201000003"]),
  course("COMP2231",["201000003","201000001"])
]
true.

В вашем примере вы читаете данные из файла, но в этом примере я жестко закодировал эти данные в запрос, чтобы их можно было воспроизвести в любом месте без необходимости копировать файл. Input использует \c, см .: Синтаксис побега символов , чтобы сохранить правильное форматирование ввода.

Когда вы загружаете данные из файла и вы не с использованием библиотеки (dcg / basics) использовать фраза_from_file / 2 или фраза_from_file / 3 . Когда вы загружаете данные из файла, и вы , используя библиотеку (dcg / basics) , используйте read_file_to_codes / 3 . Также проверьте open_string / 2 , который может быть полезен.

Вы правильно использовали library(dcg/basics), но будьте очень осторожны при использовании этого, так как предикаты там ожидают, что входные данные будут уставными коды, а не атомы или строки.

Один предикат, который очень часто используется при разборе текста с DCG, - это string_without // 2 , но, как я заметил, он работает с кодами символов, так что string_codes / 2 необходим для преобразования кодов обратно в строку. Кроме того, поскольку string_codes / 2 является стандартным предикатом, его необходимо добавить в {}, чтобы код перезаписи термина DCG знал, что его нельзя переводить.

При создании Например, я мог бы добавить \n после последнего ученика, добавить дополнительную строку и сделать синтаксический анализатор очень простым, но вместо этого предпочел следовать более реальному соглашению об отказе от добавления \n, которое требовало добавления ; ( или) части, например ; [] для последней пропущенной пустой строки и ; remainder//1 для пропущенной \n после последнего ученика.

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


Я действительно борюсь с просто I / O

Вот модифицированная версия кода, которая использует read_file_to_codes / 3 .
Обратите внимание, что read_file_to_codes / 3 является одним из немногих предикатов, который использует путь к файлу / имя напрямую и не требует использования open / 3

File : SO_question_163_courses.txt

MATH2221
       201000001
       201000002

MATH2251
       201000002
       201000003

COMP2231
       201000003
       201000001
:- [library(dcg/basics)].

courses([Course|Courses]) -->
    course(Course),
    courses(Courses), !.
courses([]) --> [].

course(course(Course,Students)) -->
    string_without("\n", Course_codes),
    { string_codes(Course,Course_codes ) },
    "\n",
    students(Students),
    (
        empty_line
    ;
        []
    ).

students([Student|Students]) -->
    student(Student),
    students(Students).
students([]) --> [].

student(Student) -->
    spaces_or_tabs_plus,
    (
        (
            string_without("\n",Student_codes),
            { string_codes(Student,Student_codes) },
            "\n"
        )
    ;
        remainder(Student_codes),
        { string_codes(Student,Student_codes) }
    ).

spaces_or_tabs_plus -->
    space_or_tab,
    spaces_or_tabs_star.

spaces_or_tabs_star -->
    space_or_tab,
    spaces_or_tabs_star.
spaces_or_tabs_star --> [].

space_or_tab -->
    (
        "\s"
    |
        "\t"
    ).

empty_line --> "\n".

example_01 :-
    Input = "\c
MATH2221\n\c
    \t201000001\n\c
    \t201000002\n\c
    \n\c
MATH2251\n\c
    \t201000002\n\c
    \t201000003\n\c
    \n\c
COMP2231\n\c
    \t201000003\n\c
    \t201000001\c
",
    string_codes(Input,Codes),
    parse_courses(Codes,Courses),
    display_courses(Courses).

example_02 :-
    File_name = "C:\\Users\\Groot\\Documents\\Projects\\Prolog\\SO_question_163_courses.txt",
    read_file_to_codes(File_name,Codes,[]),
    parse_courses(Codes,Courses),
    display_courses(Courses).

parse_courses(Codes,Courses) :-
    DCG = courses(Courses),
    phrase(DCG,Codes,Rest),
    assertion( Rest == [] ).

display_courses(Courses) :-
    format('Courses: ~n',[]),
    print_term(Courses,[]).

и некоторые примеры работает

?- example_01.
Courses: 
[ course("MATH2221",["201000001","201000002"]),
  course("MATH2251",["201000002","201000003"]),
  course("COMP2231",["201000003","201000001"])
]
true.

?- example_02.
Courses: 
[ course("MATH2221",["201000001","201000002"]),
  course("MATH2251",["201000002","201000003"]),
  course("COMP2231",["201000003","201000001"])
]
true.


Примечание с SWI-Prolog: Тип строки и ее синтаксис в двойных кавычках

При использовании SWI-Prolog с версией 7 или выше значение двойных кавычек и обратных кавычек изменений и примеров Prolog DCG, найденных в StackOverflow, в блогах, газетах и ​​т. д. c. иногда работают, как представлено, а иногда и терпят неудачу. Начинающему новичку, похоже, не будет причин для этого, и он будет очень расстраивать.

Чтобы решить эту проблему, нужно знать значения для двух флагов Prolog :

двойные кавычки и обратные кавычки

двойные кавычки обычно будут одним из codes,chars,atom,string
обратных кавычек как правило, будет одним из codes,chars,string

Вам нужно будет определить, на что их установить для кода, который вы используете, либо набрав опыт, либо просто методом проб и ошибок.

Также с Prolog при создании тестового примера с использованием

:- begin_tests(some_dcg).

:- end_tests(some_dcg).

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

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

Теперь, чтобы сделать его еще более запутанным, иногда настройка в разделе DCG не такая, как в тесте случаи, так что имейте это в виду.

Ниже приведен пример DCG, в котором есть тестовые случаи, установлены оба флага в каждом модуле и работает.

:- module(course,
      [ courses//1,
        parse_courses/2,
        display_courses/1,
        test_course/0
      ]).

test_course :-
    run_tests([course]).

:- [library(dcg/basics)].

:- set_prolog_flag(double_quotes, string).
:- set_prolog_flag(back_quotes, codes).

courses([Course|Courses]) -->
    course(Course),
    courses(Courses), !.
courses([]) --> [].

course(course(Course,Students)) -->
    string_without("\n", Course_codes),
    { string_codes(Course,Course_codes ) },
    "\n",
    students(Students),
    (
        empty_line
    ;
        []
    ).

students([Student|Students]) -->
    student(Student),
    students(Students).
students([]) --> [].

student(Student) -->
    spaces_or_tabs_plus,
    (
        (
            string_without("\n",Student_codes),
            { string_codes(Student,Student_codes) },
            "\n"
        )
    ;
        remainder(Student_codes),
        { string_codes(Student,Student_codes) }
    ).

spaces_or_tabs_plus -->
    space_or_tab,
    spaces_or_tabs_star.

spaces_or_tabs_star -->
    space_or_tab,
    spaces_or_tabs_star.
spaces_or_tabs_star --> [].

space_or_tab -->
    (
        "\s"
    |
        "\t"
    ).

empty_line --> "\n".

parse_courses(Codes,Courses) :-
    DCG = courses(Courses),
    phrase(DCG,Codes,Rest),
    assertion( Rest == [] ).

display_courses(Courses) :-
    format('Courses: ~n',[]),
    print_term(Courses,[]).

:- begin_tests(course).

:- set_prolog_flag(double_quotes, string).
:- set_prolog_flag(back_quotes, codes).

test(001) :-
    Input = "\c
        MATH2221\n\c
            \t201000001\n\c
            \t201000002\n\c
            \n\c
        MATH2251\n\c
            \t201000002\n\c
            \t201000003\n\c
            \n\c
        COMP2231\n\c
            \t201000003\n\c
            \t201000001\c
        ",
    string_codes(Input,Codes),
    parse_courses(Codes,Courses),

    assertion( Courses ==
        [
            course("MATH2221",["201000001","201000002"]),
            course("MATH2251",["201000002","201000003"]),
            course("COMP2231",["201000003","201000001"])
        ]
    ).

test(002) :-
    File_name = "C:\\Users\\Groot\\Documents\\Projects\\Prolog\\SO_question_163_courses.txt",
    read_file_to_codes(File_name,Codes,[]),
    parse_courses(Codes,Courses),

    assertion( Courses ==
        [
            course("MATH2221",["201000001","201000002"]),
            course("MATH2251",["201000002","201000003"]),
            course("COMP2231",["201000003","201000001"])
        ]
    ).

:- end_tests(course).

Запуск тестовых случаев

?- run_tests.
% PL-Unit: course .. done
% All 2 tests passed
true.

или если у вас есть несколько тестов в нескольких файлах и вам нужно только тестировать course

?- test_course.
% PL-Unit: course .. done
% All 2 tests passed
true.

Еще одна вещь, которая может сбить с толку, это то, что при отладке с gtrace / 0 означает, что список кодов и string будет представлен в виде строки с двойными кавычками, например, «это строка», то есть способ отличить их друг от друга is

  1. В разделе Bindings будет список связанных переменных, найдите переменную и щелкните ее правой кнопкой мыши.
  2. Появится всплывающее диалоговое окно, выберите Подробности
  3. Откроется окно с с отображением значения привязки. Вверху есть опции.
  4. Снимите отметку Portray

Пример кода, используемый для следующих примеров

dcg_test :-
    String = "string",
    Codes = [65,66,67],
    Atom = 'abc',
    dcg_test(String,Codes,Atom).

dcg_test(String,Codes,Atom) :-
    true.

Привязки

enter image description here

Пример строки

enter image description here

Пример кода

enter image description here

Если вам интересно, почему никто не рассказывает вам эти вещи о DCG, я только что сказал; Вы должны попытаться изучить это, не зная этого, мне потребовались месяцы, чтобы понять все это.


Примечания:

Я пытался сделать это, используя phrase_from_file/3 с dcg/basics, но dcg/basics ожидается закрытый список и phrase_from_file/3 создает ленивый список , и при обработке кода он превращается в переписывание предикатов в dcg/basics и имеет дело с конец потока проблемы, которые являются одними из самых больших проблем при изучении DCG.

...