Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Here you go!

A TXR Lisp macro for-tree for traversing a tree, given a variable, an expression for the root node, and expressions for how to to left and right relative to the variable.

A node structure that for-tree knows nothing about. No iterator abstraction or API, nothing.

  (defstruct node ()
    value
    left
    right)
Example tree (a balanced binary tree whose numeric values happen to be sorted):

  (defvar example-tree #S(node value 5
                               left #S(node value 3
                                            left #S(node value 1)
                                            right #S(node value 4))
                               right #S(node value 7
                                             left #S(node value 6)
                                             right #S(node value 9)))
Walk it:

  (for-tree nod example-tree nod.left nod.right
    (prinl nod.value))

  1
  3
  4
  5
  6
  7
  9
Code:

  (defmacro go-leftmost (var root left-expr stack)
    ^(for ((,var ,root)) (,var) ((set ,var ,left-expr))
       (push ,var ,stack)))

  (defmacro for-tree (var root left-expr right-expr . body)
    (with-gensyms (stack node left right)
      ^(let (,stack)
         (go-leftmost ,var ,root ,left-expr ,stack)
         (for ((,var (pop ,stack)))
              (,var)
              ((iflet ((,right ,right-expr))
                 (go-leftmost ,var ,right ,left-expr ,stack))
               (set ,var (pop ,stack)))
           ,*body))))
The left-expr and right-expr must be expressions that involve the var variable; the construct evaluates these to find the left or right child. When the value is nil it means there is no child.

This is almost exactly what the blog is asking for, transliterated to Lisp syntax.

Original concept in fantasy C syntax:

  for_tree(Node* N = mytreeroot; N != NULL; N : {N->left, N->right}
  {
    print(N->value);
  }
Lispified:

  (for-tree nod example-tree nod.left nod.right
    (prinl nod.value))
As required, the construct specifies the node variable to be the loop dummy, the root expression giving its initial value and the two accessor expressions for left and right traversal.

The termination test N != NULL is made implicit in the Lisp version, so early termination requires a break out of the loop. It could be arranged.

The fantasy C syntax specifies a variable name in the navigation declarator: N : { N->left, N->right }. Presumably, the variable here can be renamed to anything you want; it just arbitrarily happens to have the same name as the loop dummy.

I didn't replicate this feature exactly because it just adds verbosity for nothing. It's okay if the navigation declarator just refers to the loop's one and only dummy variable.

Anyway, programming languages should definitely not have a tree traversal primitive. Rather, languages should be Lisp.



Here is the same for-tree macro processing TXR Lisp's built-in search tree type. That does not use structs, but built in node and tree types.

The left, right and key fields of a node object are accessed with like-named functions. We must call tree-root on a tree object to get to the encapsulated root node:

  1> (for-tree n (tree-root #T(() 1 2 3 4 5 6 7 8)) (left n) (right n)
       (prinl (key n)))
  1
  2
  3
  4
  5
  6
  7
  8
  
How about a tree of integers. Let's say the root integer is 1, and to go left is to multiply by 2, and to go right is to multiply by 2 and mask in a 1. And let's say the depth caps out at the value 16:

  2> (for-tree n 1 (if (< n 8) (* 2 n)) (if (< n 8) (+ 1 (* 2 n)))
       (prinl n))
  8
  4
  9
  2
  10
  5
  11
  1
  12
  6
  13
  3
  14
  7
  15
  nil
Let's see that in binary:

  3> (for-tree n 1 (if (< n 8) (* 2 n)) (if (< n 8) (+ 1 (* 2 n)))
       (format t "~b\n" n))
  1000
  100
  1001
  10
  1010
  101
  1011
  1
  1100
  110
  1101
  11
  1110
  111
  1111
  nil
Let's right-align digits to see different patterns in it:

  2> (for-tree n 1 (if (< n 8) (* 2 n)) (if (< n 8) (+ 1 (* 2 n)))
       (format t "~6b\n" n))
    1000
     100
    1001
      10
    1010
     101
    1011
       1
    1100
     110
    1101
      11
    1110
     111
    1111
  nil
Nah, left-aligned wins.


I forgot I can remove the outer parentheses, and also use infix with C-like operators, since I have those modes turned on in the REPL. (Infix is an unreleased feature, coming in TXR 300).

  3> for-tree n 1 (if (n < 8) (n << 1)) (if (n < 8) (n << 1 | 1))
       (format t "~b\n" n)
   1000
   100
   1001
   10
   [...]




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: