【问题标题】:LISP Appropriate Way to Return Value From FunctionLISP 从函数返回值的适当方式
【发布时间】:2018-08-12 18:15:14
【问题描述】:

因此,我正在研究 Paul Graham 的 Common Lisp 并且有一个问题要求创建一个联合函数,该函数维护被联合化的列表中元素的顺序。为此,我编写了以下函数:

(defun new-union (listA listB)
  (setq retset (list (car listA)))
  (loop for el in (append (cdr listA) listB)
    do (if (not(member el retset))
      (push el (cdr (last retset)))))
  (return-from new-union retset))

这会在保持顺序的同时返回每个列表的唯一元素,因此如果我创建并运行:

(setq listA '(a b c a))
(setq listB '(c d e))
(new-union listA listB)

返回是:

(A B C D E)

所以第一件事是我收到编译器警告:"undefined variable: RETSET" 在这一行:(setq retset (list (car listA)))。另一件事是,上述方法似乎是一种更“面向对象”的做事方式,而不是带有return-from 语句的LISP 方式。

是否可以以更“适合lisp”的方式编写此代码而不会出现编译器错误?

*编辑:使用@Sylwester 的答案,我重写了如下函数并且没有错误:

(defun new-union (listA listB)
 (let ((retset (list (car listA))))
   (loop for el in (append (cdr listA) listB)
         do (if (not (member el retset))
              (push el (cdr (last retset)))))
   retset))

【问题讨论】:

  • 您的函数尝试将项目推送到链表的末尾。这不是单链表的好用处。

标签: functional-programming common-lisp sbcl


【解决方案1】:

setq 是更新现有绑定,并且您的变量 retset 未创建。标准中未指定如何处理此问题,因此您不能依赖涉及它的代码。您可以使用defparameter and defvar 创建全局变量,而您可以在函数中使用&aux 创建局部变量,letloop 可以使用with 创建变量。因此:

(defun new-union (list-a list-b)
  (let ((retset (list (car list-a))))
    ...
    retset
    ))

和这个使用&aux是一样的:

(defun new-union (list-a list-b &aux (retset (list (car list-a))))
  ...
  retset
  )

也和loop with clause一样:

(defun new-union (list-a list-b)
  (loop :with retset := (list (car list-a))
     ...
     :finally (return retset))) 

关于返回值。在尾部位置,评估的值是返回值。例如。

(if (< 3 4)
    8
    10)

这里返回8。这意味着您的代码中位于尾部位置的(return from new-union retset) 可能只写为retset

现在,如果您的代码不在尾部位置并且您希望早点返回,您可以执行您在尾部位置所做的操作,它会起作用。

我使用的 (return retset) 从最近的未命名 (nil) 块返回,而 return-from 从已命名块返回。 loop 有关键字named,它允许您选择它生成的块的名称。

请一个 lisper 实现这样一个微不足道的功能,你会得到很多答案。有了你的规范和测试,我会做的:

(defun new-union (&rest lists &aux (hash (make-hash-table :test 'equal)))
  (loop :for list :in lists
        :nconc (loop :for element :in list 
                     :if (gethash element hash t)
                         :collect element
                         :do (setf (gethash element hash) nil))))

【讨论】:

  • 虽然我发现您的最终实现与我的解决方案相去甚远,但我确实将setq 行更改为let。您可以在我的编辑中看到我的固定实现。
  • Return 从名为 nil 的块返回,而不是从任何块返回。
  • @Svante 已更新。仍然从最近的 obe 返回,bur return-from
  • 确切地说,(return …)(return-from nil …) 的作用相同。每个块都有一个名称(它不是可选的)。 Loop 使用默认名称 nil
  • @Svante 是的,但是如果您将块命名为 nil,这可能会产生副作用。 (loop :named nil :for lst :in lsts :do (loop :for e :in lsts :when (eq needle e) :do (return-from nil t))
【解决方案2】:

一个稍微好一点的基于列表的版本:

  • 两个以上列表的联合
  • 不使用 LAST
  • 仍在使用 MEMBER
  • 不使用 APPEND
  • DOLIST 返回值 RETSET

代码

(defun new-union (&rest lists
                  &aux (retset (list (caar lists)))
                       (rretset retset))
  (dolist (list lists retset)
    (dolist (el list)
      (unless (member el retset)
        (setf (cdr rretset) (list el)
              rretset (cdr rretset))))))

【讨论】:

    猜你喜欢
    • 2022-11-27
    • 2016-11-01
    • 1970-01-01
    • 2014-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-22
    相关资源
    最近更新 更多