Replace elements in nested quoted lists adds new e

2019-01-29 09:41发布

问题:

I have a nested list, and I am trying to non-destructively replace all its elements (inside the nested list as well). That is, given my input list

'(1 '(2 3 4) '(5 6 7) 8 9)

I am trying to achieve

'(0 '(0 0 0) '(0 0 0) 0 0)

I tried the following

 (defun subs-list (list value)
  "Replaces all elements of a list of list with given value"
  (loop for elt in list
       collect (if (listp elt)
           (subs-list elt value) 
           value)))

But when I try

(subs-list '(1 '(2 3 4) '(5 6 7) 8 9) 0)
(0 (0 (0 0 0)) (0 (0 0 0)) 0 0)

is the output I get. What am I doing wrong?

回答1:

Mark's answer and wdebeaum's answer explain why you're getting the results that you're getting; the nested quotes mean you've actually got a list like (1 (quote (2 3 4)) (quote (5 6 7)) 8 9), and you're replacing the symbol quote with 0, and that's why you get (0 (0 (0 0 0)) (0 (0 0 0)) 0 0). You probably want just '(1 (2 3 4) (5 6 7) 8 9) with no nested quotes.

It's worth pointing out that Common Lisp already provides a functions for non-destructively substituting in cons-trees, though: subst, subst-if, and subst-if-not. There are destructive versions, too: nsubst, nsubst-if, and nsubst-if-not. In particular, for this case you can just replace everything that's not a list with 0, either by using the complement function with listp and subst-if, or using listp and subst-if-not:

;; With `extra' elements because of the quotes:

(subst-if-not 0 #'listp '(1 '(2 3 4) '(5 6 7) 8 9))
;=> (0 (0 (0 0 0)) (0 (0 0 0)) 0 0) 

(subst-if 0 (complement #'listp) '(1 '(2 3 4) '(5 6 7) 8 9))
;=> (0 (0 (0 0 0)) (0 (0 0 0)) 0 0) 
;; With no `extra' elements:

(subst-if-not 0 #'listp '(1 (2 3 4) (5 6 7) 8 9))
;=> (0 (0 0 0) (0 0 0) 0 0)

(subst-if 0 (complement #'listp) '(1 (2 3 4) (5 6 7) 8 9))
;=> (0 (0 0 0) (0 0 0) 0 0)

If you wanted to take the hybrid approach suggested in wdebeaum's answer where you don't replace quotes, you can do that do:

(subst-if 0 (lambda (x)
              (not (or (listp x)
                       (eq 'quote x))))
          '(1 '(2 3 4) '(5 6 7) 8 9))
;=> (0 '(0 0 0) '(0 0 0) 0 0)

(subst-if-not 0 (lambda (x)
                  (or (listp x)
                      (eq 'quote x)))
          '(1 '(2 3 4) '(5 6 7) 8 9))
;=> (0 '(0 0 0) '(0 0 0) 0 0)


回答2:

What am I doing wrong?

You have actually done a good work with loop and it works! Remember that ' stands for quote, so:

'(1 '(2 3 4) '(5 6 7) 8 9)

is equal to

(quote (1 (quote (2 3 4)) (quote (5 6 7)) 8 9))
;       |  |      | | |    |      | | |   | |
       (0 (0     (0 0 0)) (0     (0 0 0)) 0 0)

you see, your quotes have been substituted too (except for the first one, which has been consumed during evaluation of the function argument)! One quote is enough to suspend execution.



回答3:

You have to realize that 'foo is syntactic sugar for (quote foo), so when you use quotes inside an already-quoted list, this:

'(1 '(2 3 4) '(5 6 7) 8 9)

evaluates to this:

(1 (quote (2 3 4)) (quote (5 6 7)) 8 9)

So when you substitute all the list elements with 0, you get:

(0 (0     (0 0 0)) (0     (0 0 0)) 0 0)

You either need to not put extra quotes in your examples, or you need to handle the quote operator specially in subs-list:

(defun subs-list (list value)
  "Replaces all elements of a list of list with given value"

  (loop for elt in list
       collect
         (cond 
           ((listp elt) 
             (subs-list elt value))
           ((eq 'quote elt)
             elt)
           (t
             value))))