Search code examples
listlispcommon-lisplevels

Lisp: list of nodes on a given level k


I want to find all atoms from a tree, which are situated on a given level k. I tried something like:

(defun atoms (l)
  ;returns the list of atoms of l at its superficial level
  (cond
    ((null l) l)
    ((or (atom l) (numberp l)) l)
    ((or (atom (car l)) (numberp (car l))) 
      (append (list(car l)) (atom (cdr l))))
    (T (atoms (Cdr l)))))

(defun findat (l pos k)
  (cond
    ((null l) l)
    ((= k pos) (atoms l))
    ((and (or (atom l) (numberp l)) (= k pos)) l)
    (T (cons '() (mapcar #'(lambda (l) (findat l (+ pos 1) k)) l)))))

so, for the sample: l=(a (b (g)) (c(d(e)) (f))), pos=0 and k=2, I should get the result: (g d f), but instead, I get some error saying that "A is not of type LIST". Does anyone have any idea, how to fix my code? Thanks in advance!


Solution

  • Here's a somewhat modified version of your findat. You might want to think of a better name for the function (at least write it out fully (find-atoms) instead of abbreviating unnecessarily).

    (defun findat (l k)
      "Find atoms on level K in list L."
      (cond
        ((atom l) '())
        ((= k 0) (remove-if (complement #'atom) l))
        (t (mapcan #'(lambda (l)
                       (findat l (1- k)))
                   l))))
    
    (findat '(a (b (g)) (c (d (e)) (f))) 2)
    ; => (G D F)
    

    Changes:

    • I removed the argument pos. It's easier to simply count down with k.
    • I changed the first case to check for atoms (which includes nil). It returns an empty list so that the append in the last case will discard it. Actually returning the wanted atoms is handled by the second case.
    • I used the standard remove-if instead of your atoms. The call to complement produces a predicate that matches anything that is not an atom. Thus the remove-if only keeps atoms. You could also use remove-if-not and leave out the complement, but that's deprecated.
    • In the last case, I used (reduce #'append ...) to create a flat list of the atoms. Edit: Changed it to use mapcan.

    Notice also how I put a string at the beginning of the function. That's a docstring. You should do that instead of putting a comment there like in your atoms. That way you can use (describe #'findat) to see the documentation (or you can use your Emacs/IDE to view it).