如何在Prolog中使用DCG

问题描述 投票:1回答:1

因此,我目前正在尝试使用包含课程和参加该课程的任何人的学生证的文本文件来实现类似于该树的功能。

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

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

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

我正在解析的文本文件看起来像这样:

MATH2221
       201000001
       201000002

MATH2251
       201000002
       201000003

COMP2231
       201000003
       201000001

我在网上阅读到,使用DCG可能是解决此问题的最佳方法,因为每个学生都以制表符开头,所以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.
parsing tree prolog swi-prolog dcg
1个回答
2
投票

虽然您所要提出的想法很简单,并且转换为DCG似乎相对简单,但实际上,它需要经验和技巧来知道如何正确有效地做到这一点。

以下内容适用于Windows 10上的SWI-Prolog(线程,64位,版本8.1.21)

:- [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,请参阅:Character Escape Syntax使输入的格式保持精美。

当从文件中加载数据并且您使用library(dcg/basics) 不是时,请使用phrase_from_file/2phrase_from_file/3。从文件加载数据并且使用library(dcg/basics) are时,请使用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

的谓词之一。

文件: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的注释:The string type and its double quoted syntax

当使用版本7或更高版本的SWI-Prolog 双引号反引号的含义更改以及在StackOverflow,博客,论文等中找到的Prolog DCG示例,有时会按提示工作,有时会失败。对于初学者来说似乎没有任何理由,并且会非常令人沮丧。

解决此问题的方法要知道两个Prolog flags的值:

双引号反引号

双引号通常为codes,chars,atom,string之一反引号通常是codes,chars,string

之一

您将需要通过积累经验或反复试验来确定将它们设置为所使用代码的方式。

在使用创建测试用例时也使用Prolog

:- begin_tests(some_dcg).

:- end_tests(some_dcg).

这将创建一个模块,并且因为标志的作用域是一个模块,这意味着如果您有多个模块,则每个模块中的标志可以不同。因此,您还必须使用测试用例模块检查/设置标志。

现在让它变得更加令人困惑,有时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将被表示为带双引号的字符串,例如“这是一个字符串”,区分它们的方法是

  1. Bindings部分中,将是绑定变量的列表,找到一个变量并右键单击它。
  2. 将出现一个弹出对话框,选择详细信息
  3. 这将显示一个带有界限值显示的窗口。顶部有选项。
  4. 取消选中肖像

用于以下示例的示例代码

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/3dcg/basics来执行此操作,但是dcg/basics预期关闭列表phrase_from_file/3创建了[[惰性列表,并且在处理代码时将其重写为dcg/basics中的谓词并处理stream of end问题,这些问题是学习DCG时最大的问题。

© www.soinside.com 2019 - 2024. All rights reserved.