Implementing a simple version of Prolog findall without using the built-in findall

762 views Asked by At

I am trying to implement a simple version of findall in Prolog without using the built-in findall or similar built-in predicates -- just as a learning exercise.

For example, suppose there is a relation a/1 in my database,

a(1).
a(11).
a(13).

and I want a relation like find_the_as(List) to give me List = [1,11,13].

I can do this with a "fail" based loop using assert and retract, but I am thinking there must be a recursive way to do this using an accumulator (and I am just not seeing it).

Thanks in advance!

for example:

a(1).
a(11).
a(13).

%
% collect all the N such that a(N) 
% into a List
%  
collect_n_such_that_a_of_n(List) :- 
   retractall(the_n_such_that_a_of_n(_)),
   assert(the_n_such_that_a_of_n([])),
   (
      a(N),
      the_n_such_that_a_of_n(As),
      retractall(the_n_such_that_a_of_n(_)),
      assert(the_n_such_that_a_of_n([N|As])),
      fail
   ) ; true,
   the_n_such_that_a_of_n(List).
   
2

There are 2 answers

0
14241354 On

Using this example:

a(1).
a(11).
a(13).

find(Input,Input) :- \+ (a(X), \+ memberchk(X,Input)).
find(Input,Output) :- a(X), \+ memberchk(X,Input), !, find([X|Input],Output). 

find_the_as(List) :- find([], List).

then the query:

find_the_as(List).

returns:

List = [13, 11, 1]

The example uses:

  • memberchk/2 - True if the first argument is a member of the list represented by the second argument.
  • \+/1 - True if the goal represented by its argument cannot be proven.

Unlike with findall, the resulting list does not contain duplicates. eg. if you add an extra a(11) then the resulting list will still only contain one 11.

Unlike with findall, the elements in the resulting list are in reverse order to which they were found (i.e. [13, 11, 1] rather than [1, 11, 13]).

0
gusbro On

Using call_nth/2 (beware of procedures which have side effects):

findall_recursive(Template, Goal, Bag):-
  findall_recursive(Template, Goal, Bag, 1).
  
findall_recursive(Template, Goal, Bag, N) :-
    copy_term(Goal-Template, CGoal-CTemplate),
    (   call_nth(CGoal, N)
    ->  succ(N, N1),
        Bag=[CTemplate|Bag1],
        findall_recursive(Template, Goal, Bag1, N1)
    ;   Bag=[]
    ).

Sample run having these facts:

a(1).
a(11).
a(13).
a(11).

?- findall_recursive(X, a(X), Bag).
Bag = [1, 11, 13, 11].