Replace elements in nested quoted lists adds new elements?

前端 未结 3 908
遇见更好的自我
遇见更好的自我 2020-12-22 09:48

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条回答
  • 2020-12-22 10:26

    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.

    0 讨论(0)
  • 2020-12-22 10:28

    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)
    
    0 讨论(0)
  • 2020-12-22 10:37

    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))))
    
    0 讨论(0)
提交回复
热议问题