避免在Prolog中追加/ 3的线性成本

时间:2011-06-08 13:45:23

标签: prolog dcg

让我们假设我们正在读取标准输入并构建已读取的所有行的列表。最后,我们需要显示那些以逗号分隔的行。

go:-
    prompt(_, ''),
    processInput([ ], Lines),
    findall(_, (member(L, Lines), write(L), write(',')), _),
    nl.

processInput(LinesSoFar, Lines):-
    read_line_to_codes(current_input, Codes),
    processInput(Codes, LinesSoFar, Lines).

processInput(Codes, LinesSoFar, Lines):-
    ( Codes \= end_of_file
    ->
        atom_codes(Line, Codes),
        append(LinesSoFar, [ Line ], LinesSoFar1),  % <---- append/3 - O(n)
        processInput(LinesSoFar1, Lines)
    ;
        Lines = LinesSoFar ).

此代码的问题是append中的processInput/3调用会花费我们O(n)。我们怎样才能避免这种成本和仍然让我们的谓词是尾递归的(因为我们可能从标准输入中读取了很多行)?

我想到我们可以用以下内容替换append

LinesSoFar1 = [ Line | LinesSoFar ],

我们可以在显示之前反转列表。但这看起来很糟糕。还有更好的方法吗?

2 个答案:

答案 0 :(得分:7)

我不考虑你提出的解决方案(在列表元素之前,然后在最后反转列表)“hacky”。具有显式差异列表的gusbro解决方案也是可以的。我认为最优雅的方法是使用DCG表示法(差异列表的隐式接口),即使用描述行列表的DCG:

read_lines -->
        { read_line_to_codes(current_input, Codes) },
        (   { Codes == end_of_file } -> []
        ;   { atom_codes(Line, Codes) },
            [Line],
            read_lines
        ).

用法:phrase(read_lines, Lines)

答案 1 :(得分:2)

您可以使用半实例化结构来完成此操作。 检查此代码:

append_init(Marker-Marker).

append_end(L-[I|NMarker], I, L-NMarker).

append_finish(L-[], L).

首先通过调用 append_init(L)来“初始化”半实例化结构。 然后,您可以通过调用 append_end(L,Item,NewList)在列表末尾添加元素。 完成添加元素后,可以调用 append_finish(L,List)来获取最终的,完全实例化的列表。

示例:

example(NL):-
  append_init(L), 
  append_end(L, a, L1), 
  append_end(L1, b, L2), 
  append_end(L2, c, L3), 
  append_finish(L3, NL).

?- example(L).
L = [a, b, c].