【问题标题】:How to ignore the first and last element on each line when reading from a text file in Prolog?从Prolog中的文本文件读取时如何忽略每行的第一个和最后一个元素?
【发布时间】:2019-03-26 03:47:51
【问题描述】:

我需要将固定格式文本文件的内容读入 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]]

【问题讨论】:

  • DCG 是你的朋友。或许需要介绍一下。 - Primer

标签: prolog


【解决方案1】:

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

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).

【讨论】:

  • 这很好用,谢谢。我很感激它不需要添加库,因为我还没有学会如何做到这一点。
  • 如果可能,您能否简要解释一下 >> 运算符的作用?我在 SWI-Prolog 文档中找到了对 (>>)/2 "Calls a copy of lambda" 的引用,但我通常觉得这个文档很难理解,这种情况也不例外。
  • 是的,文档有点简洁。所以,我将添加不带 lambda 的等效代码(library(yall) 有效地实现了 lambda 表达式,它是 SWI-Prolog 中最近添加的)
【解决方案2】:

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

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

  • 输入的第一行有两个用空格分隔的整数值;这些是“行”和“列”计数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 个不同的方向上有改进的余地。有什么不明白的,就问吧。

【讨论】:

  • 您对输入文件过程的总结是正确的。感谢您在开始编码之前先写下并澄清问题的评论;这种方法在处理未来的编码问题时肯定会有所帮助。
  • @quiconque 我早在开始编程之前就知道如何做到这一点。它被称为“解决问题”。如果您仍在学习它(并且您已超过 12 岁),那么您已经错过了火车。
【解决方案3】:

使用 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/basics 中的 module 中有一个预定义的常用谓词库。

SWI Prolog 有unit test

使用单元测试\c 更容易格式化输入数据以供读取。

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

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

  2. 中得到了证明
  3. phrase_from_file/2 通常在 DCG 工作并希望直接从文件中读取数据时使用。


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

如果你想使用调试器和使用 SWI-Prolog 的测试用例,那么你 使用

启动调试器
?- gtrace.
true.

然后运行特定的测试

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

【讨论】:

  • 使用succ(N0, N) 而不是N0 is N - 1 进行倒计时有一个很好的属性,当它到达0 时它会失败。试试succ(X, 1)succ(X, 0)succ(X, -1) 看看我的意思。
  • 我不明白你的测试方法。您可以使用测试头来完成您通常在体内执行的大部分操作。 assertions 尤其让我想知道是什么让它们变得如此必要。
  • 您正在编写测试真是太好了。问题是,你为什么要这样写?在 PlUnit 的文档中,您有很多示例,它们看起来与您正在做的不太一样。所以我很自然地假设你在做一些特别的事情,但我不够聪明,无法理解你在做的这个特别的事情是什么?
  • 一个问题(如果你愿意,你可以自己回答)可能会更好。我尝试从其他人的代码中学习,如果有一些我以前没有考虑过的新东西会很棒。
  • 不确定您是否阅读了我的问题,或者它是否写得太糟糕了。在我有限的头脑中没有明显原因的奇怪东西的例子。 phrase(X, Y)phrase(X, Y, Z), Z== [] 在我看来是一回事;但你的代码不同意。为什么?
猜你喜欢
  • 2021-07-29
  • 2017-11-20
  • 1970-01-01
  • 2021-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-29
  • 1970-01-01
相关资源
最近更新 更多