从Prolog中读取文本文件时,如何忽略每行的第一个和最后一个元素?

时间:2019-03-25 04:10:48

标签: prolog

我需要将固定格式的文本文件的内容读入Prolog中的列表列表(LL),但是我想从该行的列表中排除每行的第一个和最后一个元素。输入文件的第一行包括行数(LL中的列表数)和列数(LL中每个列表的元素数)。 带有3行4列的示例输入文件是

3 4
A B C D Cd
1 9 3   7   4   7
2 6 8   4   0   32
3 2 4   3   8   42
Ab 140  21  331 41 55

我想

LL = [[9,3,7,4],[6,8,4,0],[2,4,3,8]]

如何从LL中排除每行的第一个和最后一个元素?

我尝试阅读SWI-Prolog文档并在此处搜索相关线程,但未成功。

readAll( InStream, [W|L] ) :-
     readWordNumber( InStream, W ), !,
     readAll( InStream, L ).

readAll( InStream, [] ) :-
     \+readWordNumber(InStream,_).

lst_2_lst_of_lst([], _N, []).
lst_2_lst_of_lst(L, N, LL) :-
    lst_2_lst_of_lst_helper(L, 1, N, LL).
lst_2_lst_of_lst_helper([H|T], N, N, [[H]|LL]):-
    lst_2_lst_of_lst(T, N, LL). 
lst_2_lst_of_lst_helper([H|T], N1 , N, [[H|TMP]|LL]):-
    N2 is N1 + 1,
    lst_2_lst_of_lst_helper(T, N2 , N, [TMP| LL]).    

致电后

...readAll(F,Input), ...
lst_2_lst_of_lst(Input, C, LL)

(C为4,从文本文件F的第一行读入)

我当前的结果看起来像这样

LL = [[1,9  3   7  4 7,2,6   8   4 0 32],[3,2  4  3 8  42,Ab,140  21 331     41]]

我希望它看起来像这样

LL = [[9,3,7,4],[6,8,4,0],[2,4,3,8]]

3 个答案:

答案 0 :(得分:2)

我将解析文件和清除行的问题分开。 假设我们有一个谓词实际上捕获了令牌行。 然后可以应用以下内容:

cleanup([_,_|Data],Clean) :-
    remove_last(Data,DataC),
    maplist([[_|L],C]>>remove_last(L,C),DataC,Clean).
remove_last(L,C) :-
    append(C,[_],L).

捕获令牌行可能是

readAll(InStream,[Line|Lines]) :-
    read_a_line(InStream,Line),
    readAll(InStream,Lines).
readAll(_InStream,[]).

read_a_line(F,L) :-
    read_line_to_string(F,S),
    S\=end_of_file,
    tokenize_atom(S,L).

为说明SWI-Prolog的一些IO功能,请进行快速测试:

?- data(F),open_any(string(F),read,Stream,Close,[]),readAll(Stream,AllLines),cleanup(AllLines,Clean),Close.
F = "3 4\nA B C D Cd\n1 9 3   7   4   7\n2 6 8   4   0   32\n3 2 4   3   8   42\nAb 140  21  331 41 55",
Stream = <stream>(0x7f37b039e5d0),
Close = close(<stream>(0x7f37b039e5d0)),
AllLines = [[3, 4], ['A', 'B', 'C', 'D', 'Cd'], [1, 9, 3, 7, 4, 7], [2, 6, 8, 4, 0|...], [3, 2, 4, 3|...], ['Ab', 140, 21|...]],
Clean = [[9, 3, 7, 4], [6, 8, 4, 0], [2, 4, 3, 8]] 

其中data(F)实际上将F绑定到示例文件中的字符串。

没有lambda,我们需要一个“使用一次”的谓词:例如

cleanup([_,_|Data],Clean) :-
    remove_last(Data,DataC),
    maplist(remove_first_and_last,DataC,Clean).
    %maplist([[_|L],C]>>remove_last(L,C),DataC,Clean).
remove_first_and_last([_|L],C) :-
    append(C,[_],L).

答案 1 :(得分:1)

不确定我是否理解您的要求。您的输入看起来有点像表格数据,但是也有点像某种文件格式。哪一个?实际定义如何?示例输入的第二行/第二行的重要性是什么?是“空白”列分隔符吗?问题可以继续。

这是我将如何解释您的问题:

  • 输入的第一行有两个由空格分隔的整数值;分别是“行”和“列”计数nrowncol
  • 第二行不相关(?)。
  • 然后,随后是许多行,其中各行之间用空格分隔,各行之间有整数。对于nrow行,使列表长nrow
    • 跳过第一列;
    • 获取下ncol列,并将它们放在整数列表中。
  • 跳过其余输入。

写下来大约是辛勤工作的99%(不是说很难,但是对于这个问题,所有的“硬度”都在这里)。

现在,您可以继续进行简单的工作:编写代码。 SWI-Prolog提供了一个名为dcg/basics的小型库。有了它,我想到了这个(急忙):

$ cat ignore.pl
:- use_module(library(dcg/basics)).

read_stuff_from_stream(Stuff, Stream) :-
    phrase_from_stream(stuff(Stuff), Stream).

stuff(LL) -->
    integer(Nrow), white, whites, integer(Ncol), blanks_to_nl, !,
    string_without("\n", _Skip_this_line), "\n",
    rows(Nrow, Ncol, LL),
    remainder(_Skip_the_rest).

rows(0, _, []) --> !.
rows(Nrow, Ncol, [R|Rows]) --> { succ(Nrow0, Nrow) },
    skip_column,
    cols(Ncol, R),
    string_without("\n", _Skip_rest_of_line), "\n", !,
    rows(Nrow0, Ncol, Rows).

skip_column --> nonblanks(_Skip_this_column), white, whites.

cols(0, []) --> !.
cols(Ncol, [C|Cols]) --> { succ(Ncol0, Ncol) },
    integer(C), white, whites, !,
    cols(Ncol0, Cols).

这不是“干净的”代码,但这是一个起点。它适用于您给出的示例。

3 4
A B C D Cd
1 9 3   7   4   7
2 6 8   4   0   32
3 2 4   3   8   42
Ab 140  21  331 41 55
$ swipl -q
?- [ignore].
true.

?- setup_call_cleanup(open('example.txt', read, In), read_stuff_from_stream(Stuff, In), close(In)).
In = <stream>(0x55f44e03de50),
Stuff = [[9, 3, 7, 4], [6, 8, 4, 0], [2, 4, 3, 8]].

在10个不同方向上仍有改进的空间。如果您听不懂,请询问。

答案 2 :(得分:1)

使用DCG完成代码。

:- use_module(library(dcg/basics), except([eos/2])).       

:- set_prolog_flag(double_quotes, codes).

parse(LL) -->
    size(Rows,Columns),
    header,
    rows(Rows,Columns,LL),
    footer.

size(Row,Columns) -->
    integer(Row),
    whites,
    integer(Columns),
    "\n".

header -->
    string_without("\n",_),
    "\n".

rows(Rows0,Columns,[Item|Items]) -->
    row(Columns,Item),
    { Rows is Rows0 - 1 },
    rows(Rows,Columns,Items).
rows(0,_Columns,[]) --> [].

row(Columns,Values) -->
    integer(_), % Ignore first value
    whites,
    values(Columns,Values),
    integer(_), % Ignore last value
    "\n".

values(Columns0,[Item|Items]) -->
    value(Item),
    { Columns is Columns0 - 1 },
    values(Columns,Items).
values(0,[]) --> [].

value(Item) -->
    integer(Item),
    whites.

footer -->
    rest_of_line, !.

rest_of_line -->
    [_],
    rest_of_line.
rest_of_line --> [].

readAll(LL) :-
    phrase_from_file(parse(LL),'C:/ll.dat').

测试用例

:- begin_tests(data).

test(1) :-
    Input = "\c
        3 4\n\c
        A B C D Cd\n\c
        1 9 3   7   4   7\n\c
        2 6 8   4   0   32\n\c
        3 2 4   3   8   42\n\c
        Ab 140  21  331 41 55\n\c
    ",
    string_codes(Input,Codes),
    DCG = parse(LL),
    phrase(DCG,Codes,Rest),
    assertion( LL == [[9,3,7,4],[6,8,4,0],[2,4,3,8]] ),
    assertion( Rest == [] ).

test(2) :-
    Input_path = 'C:/ll.dat',
    DCG = parse(LL),
    phrase_from_file(DCG,Input_path),
    assertion( LL == [[9,3,7,4],[6,8,4,0],[2,4,3,8]] ).

:- end_tests(data).

测试用例示例

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

示例运行

?- readAll(LL).
LL = [[9, 3, 7, 4], [6, 8, 4, 0], [2, 4, 3, 8]].

无论何时处理列表,都应考虑使用DCG(Primer)。

数据作为字符代码处理,因此用于统一的值也必须是字符代码。人们不容易读取字符代码,因此Prolog可以选择将双引号引起来的项目转换为字符代码列表。在代码"abc"中,在编译/咨询过程中将其翻译为[97,98,99]。这是通过Prolog flag完成的。

:- set_prolog_flag(double_quotes, codes).

由于使用DCG非常普遍,因此dcg/basicsmodule中有一个预定义的公共谓词库。

SWI Prolog中有unit test

使用单元测试\c可以简化格式化输入数据以便读取的操作。

驱动DCGs的谓词是短语,但是它带来了两个非常常见的变体。

  1. phrase/2通常在未从文件读取数据时使用。在开发和测试DCG时,我也发现它很有用,因为您可以看到整个值流。当数据作为字符代码列表处理且输入为字符串时,通常会发现{/ 2 / {3}}与短语/ 2一起使用。 test(1)

  2. 中对此进行了演示
  3. string_codes/2通常在DCG工作并且想要直接从文件中读取数据时使用。


在SWI-Prolog调试器中查看单元测试。

如果要使用SWI-Prolog将调试器与测试用例一起使用,则您可以 用

启动调试器
?- gtrace.
true.

然后运行特定的测试

[trace]  ?- run_tests(data:1).