【问题标题】:Replace elements in nested quoted lists adds new elements?替换嵌套引用列表中的元素会添加新元素?
【发布时间】:2014-08-13 18:19:18
【问题描述】:

我有一个嵌套列表,我正在尝试非破坏性地替换它的所有元素(也在嵌套列表内)。也就是说,给定我的输入列表

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

我正在努力实现

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

我尝试了以下

 (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)))

但是当我尝试时

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

是我得到的输出。 我做错了什么?

【问题讨论】:

  • 您确定'(1 '(2 3 4) '(5 6 7) 8 9) 是一个有用的列表吗?我在 Lisp 的实际应用中从未见过这样的列表。嵌套的引用列表通常没有任何意义,并且表明对 Lisp 中的引用有错误的理解。
  • @RainerJoswig,你是对的,我最初被认为 ' 是 (list) 的缩写。我的实际输入是(list 1 (list 2 3 4) (list 5 6 7) 8 9)

标签: lisp common-lisp


【解决方案1】:

我做错了什么?

您实际上已经完成了与loop 的良好合作,并且有效!请记住' 代表quote,所以:

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

等于

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

你看,你的引号也被替换了(除了第一个,它在函数参数的评估过程中被消耗掉了)!一个报价就足以暂停执行。

【讨论】:

  • 啊!这就说得通了!谢谢!
  • 这里使用loop 实际上有点矫枉过正。使用标准的 subst-if,这只是 (subst-if '0 (complement #'listp) '(1 (2 3 4) (5 6 7) 8 9)) => (0 (0 0 0) (0 0 0) 0 0)
【解决方案2】:

Mark's answerwdebeaum's answer 解释为什么你会得到你得到的结果;嵌套引号意味着您实际上有一个类似(1 (quote (2 3 4)) (quote (5 6 7)) 8 9) 的列表,并且您将符号quote 替换为0,这就是您得到(0 (0 (0 0 0)) (0 (0 0 0)) 0 0) 的原因。您可能只想要 '(1 (2 3 4) (5 6 7) 8 9) 没有嵌套引号。

值得指出的是,Common Lisp 已经提供了在 cons-trees 中进行非破坏性替换的函数:substsubst-ifsubst-if-not。也有破坏性版本:nsubstnsubst-ifnsubst-if-not。特别是,对于这种情况,您可以将所有 不是 列表的内容替换为 0,方法是使用带有 listp 和 subst-if 的 complement 函数,或者使用 listp 和 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)

如果您想采用wdebeaum's answer 中建议的混合方法而不替换引号,您可以这样做:

(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)

【讨论】:

  • 我知道有一个更简单的方法,我之前看过`(subst-if),但不知道如何将“all/every”元素作为测试条件。
  • @Ash 好吧,subst 在树上运行,所以它递归到每个 cons 单元的 car 和 cdr 中。诀窍是listp 实际上并没有检查正确的列表,而只是“cons cell or nil”。在您的情况下,这些是您不想想要替换的唯一内容。
【解决方案3】:

您必须意识到'foo 是 (quote foo) 的语法糖,所以当您在已引用的列表中使用引号时,这样:

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

评估为:

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

所以当你用 0 替换所有列表元素时,你会得到:

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

您要么不需要在示例中添加额外的引号,要么需要在 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))))

【讨论】:

  • 这可以用subst-if 更简洁地处理,即使是对引号进行特殊处理。
猜你喜欢
  • 2018-04-18
  • 1970-01-01
  • 2021-04-17
  • 1970-01-01
  • 2022-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
相关资源
最近更新 更多