Here we collect only leaf nodes, depth first, leftmost first:
leaves(BinTree,Leaves) :-
Tip=Fin, % create a new "empty open list" which is just an unbound variable
leaves_dl(BinTree,Fin,FinOut), % grow the list ending in Fin, giving a new ending FinOut
FinOut=[], % close the list at FinOut; now Tip names a proper list
Leaves=Tip. % this is just about making names equal
leaves_dl(nil,Fin,Fin). % special case of empty tree
leaves_dl(t(X,nil,nil),Fin,FinOut) :- % special case of leaf node
!, % commit to this case
Fin=[X|FinOut]. % empty cell at Fin is unified
% with a new listcell with X and
% and an empty cell at FinOut
leaves_dl(t(_,L,R),Fin,FinOut) :- % case of inner node
leaves_dl(L,Fin,Fin2), % grow the list ending in Fin, giving a new ending Fin2
leaves_dl(R,Fin2,FinOut). % grow the list ending in Fin2, giving a new ending FinOut
And so:
?- leaves(t(_,t(_,t(1,nil,nil),t(2,nil,nil)),t(_,t(2,nil,nil),t(3,nil,nil))),L).
L = [1, 2, 2, 3].
This is an application of the "Difference List" applied to the "Open List" While the "Difference List" is about two variables denoting different positions in the same list, in this case we go to the extreme and have one of the variables denote the tip of the list (the first listcell), while the other denotes whatever is on second position of the last listcell (what I call the "fin"). For a closed list this is []
, for an open list, it is an "empty cell" in memory (a non-logical concept, but maybe someone has "logicized" this using modal logic at some point; it certainly seems possible).
A generic "Difference List" (really, "list difference": Pos1-Pos2)
on a proper (or closed) list
Pos1 ------->[|] ^
/ \ | "list difference" Pos2-Pos1 is
1 [|] | [1,2]
/ \ V
2 [|] <----Pos2
/ \
3 [] <--- list properly ends in []: proper list
A "Difference List" applied to an "open list" ending in an empty cell:
[|]<------- Tip (of open list) ^
/ \ |
1 [|] | "list difference" Fin-Tip
/ \ | is [1,2,3]
2 [|] |
/ \ V
3 ~empty cell~ <--- Fin
Analogous to imperative programming with pointer Fin=[X|FinOut]
grows the open list with X
, and yield a fresh variable FinOut
denoting the new fin, which is an empty cell. We can then grow it some more later:
[|]<----------- Tip (of longer open list)
/ \
1 [|]
/ \
2 [|]
/ \
3 [|] <--- Fin after unification
/ \
X ~empty cell~ <--- NewFin, a fresh variable
When we are done, we just need to unify the current Fin
with []
, closing the list:
[|]<------------ Tip (now of a proper list)
/ \
1 [|]
/ \
2 [|]
/ \
3 [|]
/ \
X [] <--- Fin unified with `[]`