从 Common-lisp 中的列表的任何级别删除所有出现的元素

Remove all occurences of elem from any level of a list in Common-lisp

我想用 mapcar/lambdas 解决这个问题。我知道如何定期这样做。到目前为止,我有类似的东西:

(defun removal (lista elem &optional final)
    (cond
       ((and (atom lista) (eql lista elem))  nil)
       ((listp lista) (mapcan (lambda (e) ( removal e elem final)) lista))
       (t (nconc final lista))))

出于某种原因,目前还没有 运行 但这是草稿。任何想法将 mapcar 放在哪里或如何摆脱可选列表 final?我需要使用映射函数或 lambda 和递归来解决这个问题。

添加了lambda和mapcan还是不能正常工作,根本不会构造列表

一些备注:

  • 当您处理多级列表时,您通常是在处理树,不是吗?我会将 removallista 替换为 tree-removetree

  • 如果 lista 是原子而不是数字怎么办?您最好在使用 = 之前检查 lista 是否为数字,或者接受 :test 参数。

  • 你说 出于某种原因,它甚至不是 运行。你没有错误信息吗?它说什么? 未定义函数:lista?您在正常评估上下文中的列表的第一个位置使用 lista 符号。

  • 为了避免使用可选参数,用labels定义一个局部函数。这是另一个演示如何使用标签的示例:

    (defun tree-occurences (tree number)
      (let ((counter 0))
        (labels ((count-occurences (form)
                   (typecase form
                     (sequence (map nil #'count-occurences form))
                     (number (when (= form number) (incf counter))))))
          (count-occurences tree))
          counter))
    

由于不清楚你有什么样的约束,这里有两个解决方案,第一个是递归的,没有高阶函数,第二个是有高阶函数的。

(defun remove-from-tree(el tree)
  (cond ((null tree) nil)
        ((consp (car tree))
         (cons (remove-from-tree el (car tree))
               (remove-from-tree el (cdr tree))))
        ((eql (car tree) el) (remove-from-tree el (cdr tree)))
        (t (cons (car tree) (remove-from-tree el (cdr tree))))))

在这个解决方案中有四种可能的情况 cond:

  1. 如果树是空的,那么return是空树,
  2. 如果树的第一个元素是cons,也就是树,递归地把函数应用到原树的car和cdr上,cons结果得到新的树,
  3. (此时我们知道树的car是一个原子),如果car是eql到元素,则忽略car,继续递归函数到cdr,
  4. 否则,用汽车(它是一个不同于元素的原子)和将函数应用于树的cdr的结果构建一棵新树。

这是第二个版本:

(defun remove-from-tree(el tree)
  (mapcan (lambda(subtree)
            (cond ((null subtree) (list nil))
                  ((consp subtree) (list (remove-from-tree el subtree)))
                  ((eql subtree el) nil)
                  (t (list subtree))))
          tree))

在本例中使用了泛函mapcan,由于这是nconc所有的结果,我们必须将list添加到cond的几个分支中内部函数。

应用于树的“顶部”元素的内部函数有四种情况:

  1. 如果子树为空,则值 returned 为 (list nil) 以便 mapcan 可以将此结果连接到其他结果,
  2. 如果子树是 cons,即树,则递归调用其上的函数和 return 结果列表,
  3. (此时我们知道子树是一个原子),如果等于要移除的元素,return nil(而这个不会出现在结果中mapcan),
  4. 最后,return作为列表的原子,不同于元素。

您提到使用 mapcarlambda,但 Common Lisp 还包括 mapcan,这在这里可能更有用。 mapcan 函数类似于 mapcar,但它不是创建函数结果列表,而是获取函数结果并将它们连接到列表中。例如,

(mapcan (lambda (x)
          (list x x))
        '(1 2 3))
;=> (1 1 2 2 3 3)

这对于过滤类型的任务很方便,因为您可以拥有映射到列表的功能总是 return 一个列表,如果它 returns nil,那么您实际上已经过滤掉了该元素。例如:

(mapcan (lambda (x)
          (if (evenp x)
              (list x)                  ; if even, return (x)
              '()))                     ; if odd, return ()
        '(1 2 3 4 5 6))
;;=> (2 4 6)

因此,您可以使用类似这样的方法从树的所有级别中删除一个元素。内部 %remove-all 函数执行实际工作,但它 始终 return 是一个列表,即使原始输入不是列表。所以,我们只需要从中提取第一个元素。

(defun remove-all (element tree &key (test #'eql))
  (labels ((%remove-all (element tree)
             (cond
               ((funcall test element tree) '())
               ((atom tree) (list tree))
               (t (list (mapcan (lambda (child)
                                  (%remove-all element child))
                                tree))))))
    (car (%remove-all element tree))))
(remove-all 3 '(1 2 3 (3 2 1 (1 3 2))))
;;=> (1 2 (2 1 (1 2)))

(remove-all 3 5)
;;=> 5

(remove-all 3 3)
;;=> NIL

(remove-all 3 '(3))
;;=> NIL