1
votes

I am new to Common Lisp and I have a problem. It sounds like this: Write the PRETTY-PRINT procedure, which takes one argument (a generalized list), and prints it using the following rule:

(

...

)

Any element, which is a list, is going to be printed using the same algorithm, recursively.

The function should print this:

(pretty-print ' ( a (b c de) fg ))

( a 

     ( b 

       c 

       de ) 

   fg )

I've tried to rewrite the function myself a couple of times and i got to this code:

(defun print-list (elements)

    (cond
        ((null elements) (princ ") ")) 
        ( t

         (cond ((listp (car elements))
                    ; (princ #\Space)
                     (princ "( ")
                     (print-list (car elements))
                     (format t "~%")

               )

         )

         (if (atom (car elements))
                   (prin1 (car elements)))
                   (format t "~%")      
                   (princ #\Space)
                   (print-list (cdr elements))

        )
    ) 
)

But its not printing what it should. Could anyone please help me with this one? I've been struggling for like a week. Thanks

1
just write the algorithm in natural language and code it laterRainer Joswig
I am not sure I understand what you mean...This is for a homework I've got and I have to submit it soon.Cat2794

1 Answers

6
votes

First step: use conventional formatting. See e. g. Practical Common Lisp.

(defun print-list (elements)
  (cond
    ((null elements) (princ ") ")) 
    (t
     (cond ((listp (car elements))
            ;; (princ #\Space)
            (princ "( ")
            (print-list (car elements))
            (format t "~%")))
     (if (atom (car elements))
         (prin1 (car elements)))
     (format t "~%")      
     (princ #\Space)
     (print-list (cdr elements)))))

The task said to name it pretty-print, and that isn't taken by any standard function, so let's just do that:

(defun pretty-print (elements)
  (cond
    ((null elements) (princ ") ")) 
    (t
     (cond ((listp (car elements))
            (princ "( ")
            (pretty-print (car elements))
            (format t "~%")))
     (if (atom (car elements))
         (prin1 (car elements)))
     (format t "~%")
     (princ #\Space)
     (pretty-print (cdr elements)))))

If we try (pretty-print '(a (b c de) fg)), we get:

A
 ( B
 C
 DE
 ) 

 FG
 ) 

At least all the symbols are there in the right order, and there seems to be only one parenthesis missing. The opening parenthesis seems to be only printed in some cases, but missing at the very start.

In order to proceed, I'd like to clean up a little. Having nested conds and ifs is in most cases not necessary; you can combine them into a single cond. For that, we note that (listp (car elements)) and (atom (car elements)) seem to be intended to be mutually exclusive. Since the null check should do an early exit, we'll do that with an explicit return in order to eliminate that nesting as well. We get the three cases then:

(defun pretty-print (elements)
  (cond ((null elements)
         (princ ") ")
         (return-from pretty-print))
        ((listp (car elements))
         (princ "( ")
         (pretty-print (car elements))
         (format t "~%"))
        ((atom (car elements))
         (prin1 (car elements))))
  (format t "~%")
  (princ #\Space)
  (pretty-print (cdr elements)))

Now, for that opening parenthesis, I'd try a simpler test case: the empty list.

(pretty-print '())

which prints

)

That's not right. But that's exactly what the first cond branch says. However, if we do a (princ "()") here, there will be an additional opening parenthesis at the end of every non-empty list.

We conflated two things here: the printing of the empty list, and finishing with printing a list. You can't discern between these if you just do a simple recursion, because every tail of a list is itself a list and the last one empty.

I'd do the conditions on the input itself. Now you can say something like “in order to print a list, print an opening parenthesis, then print the contents, then a closing parenthesis”.

(defun pretty-print (elements)
  (cond ((null elements)
         (princ "()")
         (return-from pretty-print))
        ((listp elements)
         (princ "(")
         (pretty-print-elements elements)
         (princ ")"))
        ((atom elements)
         (prin1 elements)))
  (format t "~%")
  (princ #\Space))

I moved the recursion into the list into a different function (not shown yet). Now there is an explicit match-up of the parentheses, and it cannot happen that one is printed but the other not. Here is pretty-print-elements in a recursive style (so that we do not have to introduce more advanced operators):

(defun pretty-print-elements (elements)
  (unless (null elements)
    (pretty-print (first elements))
    (pretty-print-elements (rest elements))))

Let's try these:

(pretty-print '())
()

(pretty-print '(a))
(A
 )

(pretty-print '(a (b c de) fg))
(A
 (B
 C
 DE
 )
 FG
 )

That's not very pretty yet, but at least everything matches up. There are two problems left: the closing parentheses should not be on a new line, and the elements should be aligned.

First, I move the separators between elements into the other function—it's actually part of printing the list, not of printing the element.

(defun pretty-print (elements)
  (cond ((null elements)
         (princ "()")
         (return-from pretty-print))
        ((listp elements)
         (princ "(")
         (pretty-print-elements elements)
         (princ ")"))
        ((atom elements)
         (prin1 elements))))

(defun pretty-print-elements (elements)
  (unless (null elements)
    (pretty-print (first elements))
    (terpri)
    (princ #\Space)
    (pretty-print-elements (rest elements))))

I also introduced terpri here, which prints a newline.

Now it's quite obvious to omit that separation after the last element:

(defun pretty-print-elements (elements)
  (unless (null elements)
    (pretty-print (first elements))
    (unless (null (rest elements))
      (terpri)
      (princ #\Space))
    (pretty-print-elements (rest elements))))

Again, there are more concise ways to express this, but let's keep it on a basic language level. Try it:

(pretty-print '(a (b c de) fg))
(A
 (B
 C
 DE)
 FG)

Finally, the indentation. Currently, everything gets indented one space. However, we want to indent it more the deeper we are in the recursion, so we need to keep track of the level.

(defun pretty-print (elements indentation)
  (cond ((null elements)
         (princ "()")
         (return-from pretty-print))
        ((listp elements)
         (princ "(")
         (pretty-print-elements elements (1+ indentation))
         (princ ")"))
        ((atom elements)
         (prin1 elements))))

(defun pretty-print-elements (elements indentation)
  (unless (null elements)
    (pretty-print (first elements) indentation)
    (unless (null (rest elements))
      (terpri)
      (print-indentation indentation))
    (pretty-print-elements (rest elements) indentation)))

Try it:

(pretty-print '(a (b c de) fg) 0)
(A
 (B
  C
  DE)
 FG)

Okay. This is how you could format S-Expressions. The extra whitespace shown in your question is most likely a misinterpretation from some printout.

The implementation of print-indentation, as well as making it optional to specify the initial indentation are left as exercises for the reader. You can also make each of these functions more concise by taking a look at what is actually redundant.