1

Learning lisp for the fun of it, haven't had too much of a difficult time until now, and I'm on the third lecture of this site. I'm trying to complete the exercise "Implement a function that will create a list containing members of a given binary tree in postorder." Here's my code so far:

(defun bin-tree-postorder (b)
  "Create a list containing keys of b in postorder"
  (if (bin-tree-leaf-p b)
      (list (bin-tree-leaf-element b))
    (let
        ((elmt (bin-tree-node-element b))
         (left (bin-tree-node-left b))
         (right (bin-tree-node-right b)))
  (cons
        (append (bin-tree-postorder left)
                    (bin-tree-postorder right)) elmt))))

However, it won't run, because I get an error of:

*** - APPEND: A proper list must not end with +

Here's my trace:

1. Trace: (BIN-TREE-POSTORDER '(* (+ (2) (3)) (- (7) (8))))
2. Trace: (BIN-TREE-POSTORDER '(+ (2) (3)))
3. Trace: (BIN-TREE-POSTORDER '(2))
3. Trace: BIN-TREE-POSTORDER ==> (2)
3. Trace: (BIN-TREE-POSTORDER '(3))
3. Trace: BIN-TREE-POSTORDER ==> (3)
2. Trace: BIN-TREE-POSTORDER ==> ((2 3) . +)
2. Trace: (BIN-TREE-POSTORDER '(- (7) (8)))
3. Trace: (BIN-TREE-POSTORDER '(7))
3. Trace: BIN-TREE-POSTORDER ==> (7)
3. Trace: (BIN-TREE-POSTORDER '(8))
3. Trace: BIN-TREE-POSTORDER ==> (8)
2. Trace: BIN-TREE-POSTORDER ==> ((7 8) . -)

I've tried using list instead of cons, which provides a partially correct answer in the form of a list-of-lists:

(((2 3) + (7 8) -) *)

However, the correct answer is:

(2 3 + 7 8 - *)

Could anyone who answers provide hints or pointers, instead of modified code, so I could better understand the problem? I'd rather not look at the tutorial's answers, because that won't help me learn what I'm doing wrong.

Rest assured I will turn it into a tail-recursive function after the basic recursive function is figured out.

Thank you for anyone who can help!

Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
OrangeCalx01
  • 806
  • 2
  • 7
  • 21

4 Answers4

2

append combines lists together; the element of a tree isn't a list. So either you can't use append, or you have to make a list out of element.

Thus, the second case could be written as (append (bin-tree-postorder left) (bin-tree-postorder right) (list elmt)).

Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • (list elmt) at the end of the cons form instead of elmt provides the partially correct answer. What else could I use instead of append? – OrangeCalx01 Dec 21 '15 at 01:15
  • @OrangeCalx01, note that the answer of @ScottHunter is correct, you should use `(append (bin-tree-postorder left) (bin-tree-postorder right) (list elmt))` and not `(cons (append (bin-tree-postorder left)(bin-tree-postorder right)) (list elmt))`. The first form will produce the correct answer `(2 3 + 7 8 - *)`. – Renzo Dec 21 '15 at 07:26
2

Consider the values of left, right, and elmt when these calls to cons and append happen:

  (cons
        (append (bin-tree-postorder left)
                    (bin-tree-postorder right)) elmt))))

bin-tree-postorder should return a list, so the call to append should be OK. (I know this isn't actually true yet, but we're working with the assumption that the recursive calls went OK.) So now the function is going to return the value of:

(cons <appended-postorder-results> elmt)

Now, elmt is something like the symbols +. Let's use the REPL to see what a call like this returns:

CL-USER> (cons '(2 3) '+)
((2 3) . +)

That's not what you wanted. You wanted the list (2 3 +). To get that, you don't want to cons onto elmt, because then you'll get an improper list like the one shown above. You may find Dot notation in scheme useful for understanding what that . means. Later calls to append will complain about the improper list, as you've seen:

CL-USER> (append '((2 3) . +) '(4 5))
; Evaluation aborted on #<TYPE-ERROR expected-type: LIST datum: +>.

The arguments to append have requirements that you might consider unusual if you haven't implemented append yourself. The documentation for append says:

Syntax:

append &rest lists &Rightarrow; result

Arguments and Values:

list—each must be a proper list except the last, which may be any object.

The reason is that append has to walk to the end of each of the lists (except the list) to build a new list that is ultimately terminated by the (copy of) the next list. That means "replacing" the final nil with the next list. An improper list (like (1 . 2) doesn't have nil at end.

The easiest thing you can do is to simply remove the cons, and add an argument to append, namely a list containing elmt:

(append (bin-tree-postorder left)
        (bin-tree-postorder right)
        (list elmt))
Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
1

If you don't want to use append, you could use an accumulator to build up the list in reverse. Here is code that does that.

(defun bin-tree-postorder (b &optional accumulator)
  (if (bin-tree-leaf-p b)
      (cons (bin-tree-leaf-element b) accumulator)
      (bin-tree-postorder (bin-tree-node-left b)
                          (bin-tree-postorder (bin-tree-node-right b)
                                              (cons (bin-tree-node-element b)
                                                    accumulator)))))

What the function does is append the result of post ordering the tree to the accumulator. It works recursively by first adding the current element to the accumulator, appending the post ordering of the right subtree to the accumulator, and then appending the post ordering of the left subtree to the accumulator.

malisper
  • 1,671
  • 1
  • 12
  • 16
-1

push is what you're looking for -- push new elements onto the end of the list. reverse if necessary when done.

scooter me fecit
  • 1,053
  • 5
  • 15