如何从文件中读取数据到Prolog

How to read data from file into Prolog

我正在使用 SWI-Prolog 创建一个 Wumpus World 项目。我应该从如下所示的 .txt 文件中读取黄金、矿坑和 Wumpus 的位置:

   GOLD 3 2
   WUMPUS 3 3
   PIT 2 1
   PIT 3 4

其中单词标识对象,第一个数字标识对象的 x 位置,第二个数字标识对象的 y 位置。我知道如何打开一个文件并从中读取,我只是不知道如何告诉我的程序 GOLD 3 2 意味着黄金需要位于 (3, 2)。

您需要打开文件,阅读其中的每一行,然后将每一行拆分为您要使用的术语。然后,您可以将这些术语放在某个变量中或 assert/1 将它们放入动态数据库中。在下面的示例中,我使用动态谓词 location/3:

将它们断言到数据库中
:- dynamic location/3.

load(File) :-
    setup_call_cleanup(
         open(File, read, Stream),
         load_loop(Stream),
         close(Stream)
         ).


load_loop(Stream) :-
    read_line_to_string(Stream, String),
    (String == end_of_file ->
         true
     ;
     split_string(String, " ", " ", [ItemS, XS, YS]),
     atom_string(Item, ItemS),
     term_string(X, XS),
     term_string(Y, YS),
     assertz(location(Item, X, Y)),
     load_loop(Stream)
    ).

show_locations :-
    forall(location(Item, X, Y),
           format('~p is at (~d, ~d)~n', [Item, X, Y])).

假设您的输入是 wumpus.txt,那么您将得到:

bash-3.2$ swipl -l wumpus.pl
'GOLD' is at (3, 2)
'WUMPUS' is at (3, 3)
'PIT' is at (2, 1)
'PIT' is at (3, 4)

考虑使用 library(csv),免费读取和解析文件。我把你的例子放在一个文件中 wumpus.txt:

$ cat wumpus.txt 
GOLD 3 2
WUMPUS 3 3
PIT 2 1
PIT 3 4

库文档中有一些代码示例,这里是一个最小的示例,直接来自顶层:

?- use_module(library(csv)).
true.

?- csv_read_file("wumpus.txt", World, [separator(0' ), functor(location)]),
   forall(member(L, World), assertz(L)).
World = [location('GOLD', 3, 2), location('WUMPUS', 3, 3), location('PIT', 2, 1), location('PIT', 3, 4)].

重要:表示分隔符是space字符的方法是添加选项location(0' )0' 之后和右括号之前的 space 很重要!

现在你的数据库中有一个 table,location/3,它的第一个参数是类型,第二个和第三个参数是坐标。

我想您将如何使用它是另一个问题。现在你可以问,"where do I have gold":

?- location('GOLD', X, Y).
X = 3,
Y = 2.

或者,"where do I have a pit"?

?- location('PIT', X, Y).
X = 2,
Y = 1 ;
X = 3,
Y = 4.

基于 DCG 的解决方案

我想在现有解决方案中添加一个 基于 DCG 的 解决方案。

DCG 的优势

使用 DCG 完成此任务有几个主要优势:

  • 您可以轻松地以交互方式测试您的解析器,而无需修改单独的文件。
  • 足够通用的 DCG 可用于解析以及生成 测试数据。
  • 了解此方法可能会对更复杂的解析任务派上用场,这些任务不适合 CSV 等预定格式。

预赛

以下代码假定设置:

:- set_prolog_flag(double_quotes, chars).

我推荐此设置,因为它使 DCG 的工作更具可读性。

积木:token//1

我们从 令牌 的简短定义开始:

token(T) -->
        alnum(L),
        token_(Ls),
        !, % single solution: longest match
        { atom_chars(T, [L|Ls]) }.

alnum(A) --> [A], { char_type(A, alnum) }.

token_([L|Ls]) --> alnum(L), token_(Ls).
token_([])     --> [].

示例查询

这里有几个例子:

?- phrase(token(T), "GOLD").
T = 'GOLD'.

?- phrase(token(T), "2").
T = '2'.

?- phrase(token(T), "GOLD 2").
false.

最后一个例子清楚地表明 whitespace 不能是标记的一部分。

空格

我们将以下序列视为空白

spaces --> [].
spaces --> space, spaces.

space --> [S], { char_type(S, space) }.

解决方案

因此,由空格分隔的 标记序列 是:

tokens([])     --> [].
tokens([T|Ts]) --> token(T), spaces, tokens(Ts).

就是这样!

我们现在可以透明地将此 DCG 应用于文件,使用 Ulrich Neumerkel 的远见者 library(pio) 用于:

这里是wumpus.data:

$ cat wumpus.data
GOLD 3 2
WUMPUS 3 3
PIT 2 1
PIT 3 4

使用phrase_from_file/2将DCG应用于文件,我们得到:

?- phrase_from_file(tokens(Ts), 'wumpus.data').
Ts = ['GOLD', '3', '2', 'WUMPUS', '3', '3', 'PIT', '2', '1', 'PIT', '3', '4'] .

从这样的标记列表中,很容易导出必要的数据,例如再次使用一个DCG:

data([])     --> [].
data([D|Ds]) --> data_(D), data(Ds).

data_(gold(X,Y))   --> ['GOLD'], coords(X, Y).
data_(wumpus(X,Y)) --> ['WUMPUS'], coords(X, Y).
data_(pit(X,Y))    --> ['PIT'], coords(X, Y).

coords(X, Y) --> atom_number(X), atom_number(Y).

atom_number(N) --> [A], { atom_number(A, N) }.

我们可以一起使用这些 DCG 来:

  1. 标记一个文件或给定的字符列表
  2. 解析创建结构化数据的标记。

示例查询:

?- phrase_from_file(tokens(Ts), 'wumpus.data'),
   phrase(data(Ds), Ts).
Ts = ['GOLD', '3', '2', 'WUMPUS', '3', '3', 'PIT', '2', '1'|...],
Ds = [gold(3, 2), wumpus(3, 3), pit(2, 1), pit(3, 4)] .

有关此多功能机制的更多信息,请参阅


1请注意 SWI-Prolog 附带 过时 版本的 library(pio),它不适用于 double_quotes 设置为 chars。如果你想用 SWI-Prolog 试试这个,直接使用 Ulrich 提供的版本。