Question

I've been tasked to implement a version of findall in Prolog without using any Prolog built-ins except for not and cut - so basically in pure Prolog.

I'm trying to search a tree for all direct descendants and return the results in a list

parent(a, b).
parent(b, c).
parent(b, d).
parent(e, d).

What I have so far is:

find(X, L) :- find2(X, [], L).
find2(X, Acc, L) :- parent(Y, X), find2(Y, [Y|Acc], L).
find2(_, Acc, Acc).

What I want to be getting when I enter for example:

find(a,X).

would be:

X = [b, c, d]

(Order not important)

However instead I am getting:

X = [b, c] ;
X = [b, d] ;
X = [b] ;
X = [].

I'm new to Prolog so any help on this would be much appreciated.

Thanks

Was it helpful?

Solution 3

Thanks for you help everyone. I managed to sovle it in the end by adding a predicate which checked each item against the current list, and failed if it was already present:

find(X, Loa) :- find(X, [], Loa), !.
find(X, Acc, Loa) :- dec(Y, X), uList(Y, Acc, AccNew), find(X, AccNew, Loa).
find(_, Acc, Acc).

dec(X,Y) :- parent(X,Y).
dec(X,Y) :- parent(X,Z), dec(Z,Y).

uList(X, [], [X])  :- !.
uList(H, [H|_], _) :- !, fail.
uList(X, [H|T], L) :- uList(X, T, Rtn), L = [H|Rtn].

OTHER TIPS

Besides asserting data as you go, you can also use an extra-logical predicate such as nb_setarg/3. Then once a parent is found, you fail back past nb_setarg and find another parent. All previously found solutions should stay in the term you did nb_setarg on, then after all results are exhausted, the nb_setarg term is the answer. The SWI-Prolog example is good, but its just a counter. Try doing it with a list (or better yet: difference list) that builds as you go.

Take a look at this solution. Note that this solution uses dynamic predicate named queue in order to cache all solutions until all possibilities are exhausted. Once no more solution exists, implementation retracts all facts and composes the list.

This is of course a bit simplified solution, imagine what would happen if two findall would be active at the same time. It is also a bit fragile on exact semantics of assert and retract if particular prolog implementation

Here is a solution that uses SWI-Prolog queues and threads. It uses the old existing API and does something along Tarau's Engines. I assume that the thread creation will copy the template and the goal. And then I assume that the queue send will again do a copy of each solution.

So compared to the classical findall you will have a surplus on one template and goal copy, but otherwise it will also copy each solution as the classical findall. Source on gist here. But by modding threadall2, which does the collection, one could also implement all kinds of aggregates:

% threadall(+Term, +Goal, -List)
threadall(T, G, L) :-
   message_queue_create(J, [max_size(1)]),
   thread_create(threadall3(T, G, J), _, [detached(true)]),
   thread_get_message(J, A),
   threadall2(J, A, L),
   message_queue_destroy(J).

% threadall3(+Term, +Goal, +Queue)
threadall3(T, G, J) :-
   G, thread_send_message(J, the(T)), fail.
threadall3(_, _, J) :-
   thread_send_message(J, no).

% threadall2(+Queue, +Term, -List)
threadall2(J, the(T), [T|L]) :- !,
   thread_get_message(J, A),
   threadall2(J, A, L).
threadall2(_, no, []).

Here is an example run. I hope I did the bookkeeping correctly. The thread was created with detach(true), so we do not need some handle when the thread terminates. The message queue is explicitly destroyed. Here are some example runs in SWI-Prolog, we see that it works as expected:

Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.23)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam

?- threadall(X, between(0, 5, X), L).
L = [0, 1, 2, 3, 4, 5].

?- threadall(X-Y, (between(0, 2, X), 
                   threadall(Z, between(0, 2, Z), Y)), L).
L = [0-[0, 1, 2], 1-[0, 1, 2], 2-[0, 1, 2]].

Our code only implements the usual happy path: We only implement the the/1 and no/0 message. Further since we dont use setup_call_cleanup/3 it is also not safe to use the solution with interrupts. Also the last argument is not steadfast. This is all left as an exercise for the reader to implement these additional requirements and corresponding alternate paths.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top