Nice to hear from Jonathan again. So as a greeting I'll stick the boot
in on his implementation of recursivemember :-)
Jonathan implements recursivemember like this:
> define recursivemember(item, list);
> var item, list, element;
> if list = [] then
> return(false)
> endif;
> for el in list do
> if el = item then
> return(true)
> elseif islist(el) and recursivemember(item, el) then
> return(true);
> endif
> endfor;
> return(false);
> enddefine;
I still don't much like this as a solution. For example, you can never
find [] in a tree -- but you can find all other lists. We need to take
a systematic view of the meaning of lists representing tress. We can
either adopt the view that the trees are built up from "dotted pairs", to
borrow Lisp-speak, or that the lists simply represent linear
collections.
The first view leads to this solution. But look how horribly [] gets
treated again.
define recmem( x, tree );
x = tree or
tree.islist and not( tree.null ) and
( recmem( x, tree.hd) or recmem( x, tree.tl ) )
enddefine;
Ikk. The more typical Pop-style solution is to treat the lists as being
linear collections rather than binary trees. This gives us the following
solution
define recmem( x, tree );
define recmemlist( x, list );
not( list.null ) and
( recmem( x, list.hd ) or recmemlist( x, list.tl )
enddefine;
x = tree or tree.islist and recmemlist( x, tree )
enddefine;
This is obviously an inefficient solution -- it chews up stack space
like there is no tomorrow, because no implementation of Pop does
tail-call optimisation. So you need to modify the solution like this
define recmem( x, tree );
define recmemlist( x, list );
not( list.null ) and
( recmem( x, list.hd ) or chain( x, list.tl, recmemlist ) )
enddefine;
x = tree or tree.islist and chain( x, tree, recmemlist )
enddefine;
The call to "chain" forces the currently active procedure to exit and
invoke the procedure on top of the stack. In other words this is a manual
implementation of tail-call optimisation.
As an alternative, you might choose to expand the recursion into a
loop. But that's a detail and there would be only a minor
performance gain. And you would use up more stack space. So take
your pick. Here's the above routine converted to use a loop.
define recmem( x, tree );
x = tree or
tree.islist and
lblock ;;; begin new lexical block.
lvars result = false; ;;; tree might be null.
until tree.null do
;;; Nice Pop idiom. "dest" returns head and tail of a
;;; non-empty list. So E.dest -> E is an expression that
;;; steps E down the list and leaves the head on the stack.
recmem( x, tree.dest -> tree ) -> result;
quitif( result ); ;;; shortcut when we find true.
enduntil;
result ;;; push the result.
endlblock
enddefine;
Since this version was created from the recursive version, it retains all
the behaviour we want. In particular, it treats [] as a legitimate search
target and doesn't make it a special case. One criticism, however, would
be that the algorithm doesn't immediately generalise to a representation
of trees as vectors (1D arrays).
To remedy that, we can introduce the appropriate for loop:
define recmem( x, tree );
x = tree or
x.isvector and
lblock
lvars result = false;
for i in_vector tree do
;;; Another cute idiom. ->> is an assignment that duplicates
;;; the value on the stack before the assignment.
quitif( recmem( x, t ) ->> result )
endfor;
result
endlblock
enddefine;
This version can be trivially modified to cope with any data type
that provides a loop or iteration procedure. It has no special treatment
for any element. It is efficient in both space and time, although not
quite optimal in either. And a mere 11 lines of non-commented code.
Yow!
Steve
|