在Prolog中逐行读取文件

22
我想读取一个纯文本文件,并对每一行应用一个谓词(这些谓词包含“write”来进行输出)。我该如何做?
5个回答

25

您可以使用 read 来读取流。记得调用 at_end_of_stream 来确保没有语法错误。

示例:

readFile.pl

main :-
    open('myFile.txt', read, Str),
    read_file(Str,Lines),
    close(Str),
    write(Lines), nl.

read_file(Stream,[]) :-
    at_end_of_stream(Stream).

read_file(Stream,[X|L]) :-
    \+ at_end_of_stream(Stream),
    read(Stream,X),
    read_file(Stream,L).

myFile.txt

'line 0'.
'line 1'.
'line 2'.
'line 3'.
'line 4'.
'line 5'.
'line 6'.
'line 7'.
'line 8'.
'line 9'.

因此,通过调用 main ,您将收到输出:

?- main.
[line 0,line 1,line 2,line 3,line 4,line 5,line 6,line 7,line 8,line 9]
true 

只需配置 main 。这里的输出是使用 write 的示例,当然可以根据您的请求进行配置。

我认为这个原则可以应用于回答您的问题。祝你好运。


过了一段时间,但是我在这段代码中遇到了一个错误:'uncaught exception: error(existence_error(procedure,main/0),top_level/0)'。你知道这是什么意思吗? - RK_97

18
在SWI-Prolog中,最干净的解决方案是编写一个DCG来描述一个“行”是什么,然后为每一行调用一个谓词。使用library(pio)将DCG应用于文件。
编辑:根据要求,请考虑:
:- use_module(library(pio)).

lines([])           --> call(eos), !.
lines([Line|Lines]) --> line(Line), lines(Lines).

eos([], []).

line([])     --> ( "\n" ; call(eos) ), !.
line([L|Ls]) --> [L], line(Ls).

使用示例:?- phrase_from_file(lines(Ls), 'your_file.txt')


2
我知道已经过了很长时间,但是我正在尝试这种方法,似乎需要非常长的时间。你能否提供一个使用DCGs和library(pio)的高效代码示例,以逐行读取文件?谢谢! - Shon
1
非常感谢!我之前犯了一个错误,是使用 SWI-Prolog 库(pio)文档中的示例来构建我的模型。它使用 findall/3 来获取某个模式的所有实例,但我看到你只是使用一个解析整个文件的 dcg。出于好奇,为什么我们必须使用 call(eos) 而不是一个 dcg 规则呢? - Shon
7
在DCG规则中,call//1(然后是eos/2)用于可移植地引用隐式DCG参数的全部内容。您不能使用DCG规则替代它,因为DCG规则受到翻译规则的限制,只能引用这些参数的特定部分。 "可移植"意味着这与任何特定的Prolog系统如何将DCG规则翻译为Prolog规则无关,因此它可以在支持目前由ISO起草的DCG的所有系统中工作。 - mat
再次感谢!在您的帮助下,我重新调整了我尝试解决的问题,并得出了一个相当优雅的解决方案。更重要的是,您帮助我将对DCGs的理解提升到了一个新的水平。(我已经非正式地学习它们超过一年了,但仍然感觉我的掌握有限。这是一个如此简单的概念,但也许并不那么简单?) - Shon

3

有许多种解决方案可以从文件中获取未经处理的纯文本行,这些解决方案数量更多,性能更加合理:

SWI-Prolog:

最初的回答:

read_line(S, X) :- 
   read_line_to_codes(S, L), 
   read_line2(L, X).

read_line2(end_of_file, _) :- !, fail.
read_line2(L, X) :-
   atom_codes(X, L).

Jekejeke Prolog:

:- use_module(library(stream/console)).

以下是一些时间记录,读取一份655行的文件:

最初的回答:

在读取一份655行的文件时,以下是一些时间记录:
test :-
   open('<path>', read, Stream),
   test(Stream),
   close(Stream).

test(Stream) :-
   read_line(Stream, _), !,
   test(Stream).
test(_).

SWI-Prolog:

̀?- time((between(1,100,_), test, fail; true)).
% 328,300 inferences, 0.125 CPU in 0.143 seconds (88% CPU, 2626400 Lips)
true.

Jekejeke Prolog:

?- time((between(1,100,_), test, fail; true)).
% Up 121 ms, GC 2 ms, Thread Cpu 94 ms (Current 05/07/19 17:19:05)
Yes

我猜一个将输入读入字符串而不是原子的SWI-Prolog解决方案可能会更快。但在上面的代码中,我们比较的是原子与原子之间的读取。
最初的回答:我认为使用读入字符串而不是原子的SWI-Prolog解决方案可能会更快。但在上面的代码中,我们比较的是原子与原子之间的读取。

1
在SWI-Prolog文档中有一个很好的例子:
file_line(File, Line) :-
    setup_call_cleanup(open(File, read, In),
        stream_line(In, Line),
        close(In)).

stream_line(In, Line) :-
    repeat,
    (   read_line_to_string(In, Line0),
        Line0 \== end_of_file
    ->  Line0 = Line
    ;   !,
        fail
    ).

来源:https://www.swi-prolog.org/pldoc/man?predicate=read_string/5


0
根据这里的回复,我创建了这个东西,更像是Python中的“with”:
?- read_file('test.txt', tokenize,5,L). %first 5 lines
?- read_file('test.txt', tokenize,L). %the whole file
?- read_file('test.txt', split,5,L). %just split
?- open('test.txt',read,S), read_lines(S,split,5,L), close(S).

代码:

:- module(files,[read_line/3, read_file/3,  read_file/4, read_lines/3, read_lines/4, split/2, split/3, split/4]).

:- use_module(library(pcre)).

string2atoms(Strings, Atoms) :- maplist(atom_string, Atoms, Strings).
split(Str, Lst) :- split_string(Str, " ", "", Lst).
split(Str, Separator, Lst) :- split_string(Str, Separator, "", Lst).
split(Str, Separator, Pad, Lst) :- split_string(Str, Separator, Pad, Lst).
is_empty(Str) :- re_match(Str, '^\s*$').
non_empty(Str) :- ( is_empty(Str) -> false ; true).

tokenize(String,Tokens) :- split(String,Lst), string2atoms(Lst,Tokens).

%read a line and execute a Goal on it
read_line(Stream,Goal,Args) :- 
    \+ at_end_of_stream(Stream), read_line_to_string(Stream,Str),
    %% \+ isempty(Str), call(Goal,Str,Args). 
    ( is_empty(Str) -> true ; call(Goal,Str,Args)). 

% given Stream execute Goal on every line. with the option to process only N lines
read_lines(Stream, _, _,_) :- at_end_of_stream(Stream), !. %is EOF
read_lines(_, _, 0,_) :- !. % only N lines
read_lines(Stream, Goal, N, [Res|T]) :-
    N0 is N - 1, read_line(Stream, Goal, Res), writeln(Res),
    read_lines(Stream, Goal, N0, T).

%read the whole file
read_lines(Stream, Goal, LoL) :- read_lines(Stream, Goal, 1000000, LoL).

%given file name execute Goal on every line
read_file(File, Goal, N, Res) :-
    open(File, read, Stream), read_lines(Stream, Goal, N, Res), close(Stream).
read_file(File, Goal, Res) :- read_file(File, Goal, 1000000, Res).

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接