Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid linear cost of append/3 in Prolog

Tags:

prolog

dcg

Let's assume that we're reading from standard input and building a list of all the lines that have been read. In the end, we need to display those lines, separated by commas.

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

The issue w/ this code is that the append call in processInput/3 costs us O(n). How can we avoid this cost & still let our predicate be tail-recursive (because we may be reading a lot of lines from standard input)?

It occurred to me that we could replace the append with the following.

LinesSoFar1 = [ Line | LinesSoFar ],

And we could reverse the list before displaying it. But that seems hacky. Is there a better way?

like image 884
Ashley Avatar asked Dec 28 '22 19:12

Ashley


1 Answers

I do not consider the solution you propose (prepending list elements, then reversing the list at the end) "hacky". gusbro's solution with explicit difference lists is OK as well. I think the most elegant way is to use DCG notation (an implicit interface to difference lists), i.e., use a DCG that describes the list of lines:

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

Usage: phrase(read_lines, Lines).

like image 144
mat Avatar answered Jan 18 '23 02:01

mat