【问题标题】:stable-union lisp稳定联合 lisp
【发布时间】:2012-10-11 20:15:31
【问题描述】:

需要在 lisp 中编写一个联合函数,它接受两个列表作为参数并返回一个列表,该列表是两个列表的并集,没有重复。顺序要与输入列表一致

例如:如果输入是 '(a b c) 和 '(e c d),结果应该是 '(a b c e d)

这是我目前所拥有的

(defun stable-union (x y)
  (cond
   ((null x) y)
   ((null y) x))
  (do ((i y (cdr i))
       (lst3 x (append lst3 
                       (cond
                        ((listp i) 
                         ((null (member (car i) lst3)) (cons (car i) nil) nil))
                        (t (null (member i lst3)) (cons i nil) nil)))))
        ((null (cdr i)) lst3)))

我的错误是段(null(member(car i)lst3))存在“非法函数对象”

建议?

【问题讨论】:

  • 是的,他忽略了大部分内容。 stable-union 是一个 Xemacs 库函数,所以你可以查一下。它使原始列表完全不变,并且不需要任何一个列表都是唯一的,只需从第二个列表中删除第一个列表的任何成员并保留两者的顺序即可。其中一部分隐含在他给出的部分规范中,其余部分在代码中(即使它有点损坏)。
  • 你说得对,哈希表提供了最快的解决方案,不过 :)

标签: lisp


【解决方案1】:

你的括号都弄乱了:

(defun stable-union (x y)
  (cond
   ((null x) y)
   ((null y) x)  )     END OF COND form - has no effect

  (do ((i y (cdr i))
      ^^
       (lst3 x (append lst3 
                       (cond
                        ((listp i) 
                         (  (null (member (car i) lst3)) 
                         ^^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ called as a function
                             (cons (car i) nil)           with two arguments
                             nil  ) )
                                 ^^ 
                        (t                         NEXT 3 forms have no effect
                           (null (member i lst3))
                           (cons i nil)
                           nil           )))) )
                                             ^^
        ((null (cdr i)) lst3)))

这是您可能想要的代码,带有更正的括号,并在需要的地方添加了一些 ifs:

(defun stable-union (x y)
  (cond
   ((null x) y)
   ((null y) x)
   (t
    (do ((i y (cdr i))
         (lst3 x (append lst3 
                   (cond
                     ((listp i) 
                       (if (null (member (car i) lst3))
                           (cons (car i) nil)
                           nil))
                     (t 
                       (if (null (member i lst3))
                           (cons i nil)
                           nil))))))
        ((null (cdr i)) lst3)))))

这段代码仍然存在问题。你的do 逻辑是错误的,如果它只包含一个元素,它会跳过y 中的第一个元素。无论是否需要,您都会一直致电append。请注意,调用 (append lst3 nil) 会复制 lst3 中的顶级 cons 单元格,这完全是多余的。

像你这样长的语句通常放在do 正文中,而不是在do 的局部变量的更新表单中。


但您可以在适当的情况下使用更专业的do 形式。这里很自然地使用dolist。在"wvxvw"'s lead on using hash-tables for membership testing之后,我们写道:

(defun stable-union (a b &aux (z (list nil)))
  (let ((h (make-hash-table))
        (p z))
    (dolist (i a)
      (unless (gethash i h)
        (setf (cdr p) (list i) p (cdr p))
        (setf (gethash i h) t)))
    (dolist (i b (cdr z))
      (unless (gethash i h)
        (setf (cdr p) (list i) p (cdr p))
        (setf (gethash i h) t)))))

使用我称之为“head-sentinel”的技术(z 变量预初始化为单例列表)可以极大地简化自顶向下列表构建的代码,但代价是分配一个额外的 @ 987654336@细胞。

【讨论】:

    【解决方案2】:

    错误是因为您正在尝试执行评估(null (member (car i) lst3)) 的结果。在您的 cond 表达式中,如果 i 是一个列表,那么它会尝试计算表达式

    ((null (member (car i) lst3)) (cons (car i) nil) nil))
    

    并返回结果。表达式中的第一个元素应该是一个函数,但是

    (null (member (car i) lst3))
    

    将返回一个布尔值。因此失败。您的代码结构需要注意。你错过的是你需要一个内在的cond,在那里。

    顺便说一句,如果您递归地执行它,这将是一个更简洁的函数。

    我是一个计划者,而不是一个 Lisper,但我有一点考虑。这是递归实现的框架:

    (defun stable-union (x y)
      (cond
        ((null x) y)
        ((null y) x)
        ((listp y)
         (cond 
           ((member (car y) x) (stable-union ??? (???))) 
           (t (stable-union (append x (??? (???))) (cdr y)))))
        ((not (member y x)) (append x (list y)))
        (t x)))
    

    (已编辑以更正倒数第二行中的简单输入错误,感谢 Will Ness 发现)

    【讨论】:

    • 我想你的意思是((not (member y x)) (append x (list y)))。 :)
    【解决方案3】:
    (remove-duplicates (append '(a b c) '(e c d)) :from-end t)
    

    【讨论】:

    • 这显然是课程作业。使用标准库函数来完成这项工作实际上并没有帮助。
    • union 在实现stable-union 时没有用处。也许我误会你了?
    • 即使这样做了,它对于实现稳定联合仍然没有用处。它不能是辅助函数,只能是替换/替代。
    【解决方案4】:

    因为您从 do 开始,并且因为递归解决方案会更糟糕,所以您可以这样做:

    (defun union-stable (list-a list-b)
      (do ((i list-b (cdr i))
           filtered back-ref)
          ((null i) (append list-a back-ref))
        (unless (member (car i) list-a)
          (if back-ref
              (setf (cdr filtered) (list (car i))
                    filtered (cdr filtered))
              (setf back-ref (list (car i))
                    filtered back-ref)))))
    

    这仍然是二次时间,并且行为是这样的,如果第一个列表有重复项,或者第二个列表有重复项,它们不在第一个列表中 - 它们将保留。我不确定将此函数称为“联合”是否公平,但如果列表有重复项,则必须先定义如何处理列表,然后再尝试统一它们。

    如果您对结果感兴趣,而不仅仅是锻炼,这就是您可能会做的事情。请注意,它将确保元素是唯一的,即使元素在输入列表中重复。

    (defun union-stable-hash (list-a list-b)
      (loop for c = (car (if list-a list-a list-b))
         with back-ref
         with hash = (make-hash-table)
         for key = (gethash c hash)
         with result
         do (unless key
              (if back-ref
                  (setf (cdr result) (list c)
                        result (cdr result))
                  (when (or list-a list-b)
                    (setf back-ref (list c)
                          result back-ref)))
              (setf (gethash c hash) t))
         do (if list-a (setf list-a (cdr list-a))
                (setf list-b (cdr list-b)))
         do (unless (or list-a list-b)
              (return back-ref))))
    

    【讨论】:

    • 通常我们可以通过使用 head-sentinel 技术来简化代码:(defun uni (a b &aux (z (list nil))) (let ((p z)) (dolist (i b (append a (cdr z))) (unless (member i a) (setf (cdr p) (list i) p (cdr p))) )))。 (这不是任何形式的批评,而只是作为一般性评论。):) 此外,您的(u-s-hash nil nil) 返回(nil)。我不知道是否可以通过改组 LOOP 的子条款来解决它......只是添加一个额外的检查似乎不优雅......也许使用两个 dolist 循环而不是更可取。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多