简体   繁体   中英

CAR represent *right* subtree of a cons?

I am reading "ANSI Common Lisp" by Paul Graham, in $3.8 (page 40) it goes:

"Conses can also be considered as binary trees, with the car representing the right subtree and the cdr representing the left."

To me it sounds like the "right" and "left" were used wrongly at opposite positions....then I took a look of the online errata but it's not one of the items listed there...

http://www.paulgraham.com/ancomliser.html

Can anyone help to clarify this?

Btw, on the next page (p41), Paul said that "Binary trees without interior nodes are not useful for much." I don't fully understand it either. what does it mean by "without interior nodes"? A deeply nested list can have as many internal/interior nodes (ie, conses) as you want, why he said "without"? or probably he just meant that these interior nodes, if present, does not contain atom values?

Thanks, /bruin

In Lisp syntax, car is indeed the left half of a pair cdr the right half.

As for interior nodes, I suppose Graham means unlabeled interior nodes. Eg you will much more often encounter trees like

(+ (* x y) z)

which should be thought of as trees built out of triples, even though implemented in terms of pairs, than trees of the form

((a . b) . (c . d))

Conses can also be considered as binary trees, with the car representing the right subtree and the cdr representing the left.

You're right to pick up that this seems unconventional. It should probably say that the car would represent the left subtree and that the cdr would represent the right subtree. That said, trees (as well as lists) built from cons cells are intensional data structures. We make a decision about how to to represent structures using cons cells, and then think in terms of that representation, not in terms of the cons cells. So, when we're writing list processing code, we'd prefer

(defun mapcar1 (fn list)
  (cons (funcall fn (first list))
        (mapcar fn (rest list))))

to

(defun mapcar1 (fn list)
  (cons (funcall fn (car list))
        (mapcar fn (cdr list))))

because we think of a list as having a first element, and there being a rest of the list., even though both would work. It's just a coincidence that because of implementation, car and cdr would work too. (Of course, this is a leaky abstraction when we notice that we have functions named, eg, mapcar and nthcdr instead of map (well, there is a map function, but it's a little different) and nthrest or nthtail .

Similarly, we'd have to make a choice about representing binary trees with cons cells. It doesn't really make a difference whether you do:

(defun make-tree (left right)
  (cons left right))

(defun left (tree)
  (car tree))

(defun right (tree)
  (cdr tree))

or

(defun make-tree (left right)
  (cons right left))

(defun left (tree)
  (cdr tree))

(defun right (tree)
  (car tree))

because you ought to be using make-tree , left , and right rather than cons , car , and cdr when you're working with trees.

Binary trees without interior nodes are not useful for much.

He's referring to binary trees whose internal nodes don't have values of their own. For instance, in a binary search tree, a node doesn't only have a left and right subtree, it has an associated value (or element). A binary search tree node is really a triple (element, left, right), not a pair (left, right). Some problems can be solved with binary trees whose nodes don't have an associated value, but for many problems, you'll want nodes that hold two pointers (to left and right subtrees) in addition to a value.

If you represent a (binary) tree with some form of nodes, we have several possible ways to represent them in Common Lisp.

We could define a node structure:

(defstruct node left right)

Then we have a function MAKE-NODE .

We could define a node class:

(defclass node ()
  (left right))

Nodes would be made with (make-instance 'node) .

We could make nodes with lists:

(defun make-node (left right)
  (list left right))

Above uses two cons cells.

We could make nodes for a binary tree a bit smaller with just one cons cell:

(defun make-node (left right)
  (cons left right))

We could make nodes as a vector:

(defun make-node (left right)
  (vector left right))

There are many possibilities. If the node should also have some other information, then the single cons cell approach is not enough. Something like a structure or class is fine, since the data item can have more information and the object knows its type: we can easily ask (node-p something) and get a useful answer.

The style rules for choosing the right data structure for any slightly more advanced software you want to write:

  • by default use CLOS
  • if CLOS is too slow (for example slot access is too slow), then try to optimize CLOS
  • if CLOS is still too slow, then use structures. Slot access should be faster
  • try to avoid representing data structures as untyped lists or cons trees

IMO, it would have been better if the text had said simply:

Conses can also be considered as binary trees, with the car representing one subtree and the cdr representing the other subtree.

Which is chosen for left and which for right is arbitrary. (Of course, consistency etc.) Perhaps the text had already introduced a concrete representation? Or perhaps it did so later, and needed to refer to which was which? If not -- if that statement was all there was, then I think there is no reason to choose sides.

He is just being cute when he asserts that the car is the right and the cdr is the left?

Recall that car/cdr arose originally on the IBM 704. On that machine the car, ie the address part, of the words was the right side. Figure 4 on page 8 of this one of the 704 manuals illustrates that. So I guess his presentation has some historical validity.

But I'd be curious if he actually has illustrations of lists like '(abcd) drawn out using his left/right convention.

It is an error. The reason why it is an error is that conses correspond to a printed notation, and that printed notation goes left to right in a particular way.

Example:

(a b c d) <==> (a . (b . (c . (d . nil))))

This corresponds to the tree structure:

   .
  / \
 a   .
    / \
   b   .
      / \
     c   .
        / \
       d   nil

A tree is not a simple graph; the children are ordered.

If you say that (a . b) is a node with a left child b and right child a , it goes against the convention of the notation you are using , the meaning of left-and-right, and the direction of writing you're using.

If we are discussing Lisp, the notation is given : car -s are printed on the left, preceding the rest of the list. Mixing it up is not properly descriptive of the situation in Lisp, where we have to take into account everything, including the correspondence between symbolic expressions and internal structure.

If you're using Lisp conses to implement a binary data structure, and you do not care about any correspondence to Lisp's printed representation, you can assign left and right to car and cdr whichever way you like, of course, and the algorithm will work out. The convention in your code is not descriptive of the Lisp language, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM