【问题标题】:Lisp Swap functionLisp 交换功能
【发布时间】:2021-11-15 21:45:47
【问题描述】:

我正在尝试在 Lisp 中编写一个函数式程序,该程序需要对列表的任意两个元素进行整形并返回新列表。到目前为止,我有:

(defun swap (lst x y)
  (let ((temp (nth y lst)))
   (substitute (nth x lst) (nth y lst))
   (substitute temp (nth x lst)))

当我跑步时:(swap '(1 2 3 4 5 e 6 7 8) 5 4) 我明白了:

*** - EVAL/APPLY: too many arguments given to SWAP

【问题讨论】:

  • 如果你可以使用元素的索引,你可以使用rotatef,就像这样 - (rotatef (nth i lst) (nth j lst))
  • 错误对于提供的代码+输入没有意义

标签: list functional-programming lisp common-lisp swap


【解决方案1】:

您应该决定您的swap 是否会修改旧列表。

破坏性swap 可以用已经提到的rotatef 或一对setf 来完成:

(defun swap1 (lst i j)
  (let ((temp (nth i lst)))
    (setf (nth i lst) (nth j lst))
    (setf (nth j lst) temp)
    lst))

非破坏性swap 必须使用参数副本:

(defun swap2 (lst i j)
  (let* ((newlist (copy-tree lst))
         (temp (nth i newlist)))
    (setf (nth i newlist) (nth j newlist))
    (setf (nth j newlist) temp)
    newlist))

【讨论】:

  • copy-list 应该足够了,该函数只改变列表中的第一级深度,如果列表中的 sullists 被深度复制可能会出现问题
【解决方案2】:

备注

您的代码看起来有点像受 Scheme 的启发:您将变量命名为 lst 并引入了一个局部变量,就像使用 define 一样,但在 Common Lisp 中不起作用,其中 let是范围的。 此外,您没有使用SUBSTITUTE 的返回值,这意味着任何中间结果都将被丢弃。 substitute 函数纯粹是函数式的,不会因为副作用而改变其输入。

(defun swap (lst x y)
  (let (temp (nth y lst))) ;; temp is only visible inside the let
  (substitute (nth x lst)  (nth y lst)) ;; result is discarded
  (substitute temp (nth x lst))) ;; <- the only thing actually used in swap

实现交换

这个函数有两种可能的版本,一种改变输入列表,一种不改变。您还必须决定什么是有效输入或无效输入,以及在给出无效输入时会发生什么。

在以下两种情况下,我假设x 严格低于y,如果不是这种情况,您可以通过交换xy 使函数更健壮,或者简单地返回@当xy 相等时为987654334@。

还要注意,如果x 和/或y 超出列表的限制,结果将包含NIL 值或发出错误信号,您可以在以下函数中添加更多检查以避免这些问题。

破坏性版本

这里,nswap 是破坏性版本,它使用nthcdr 下降到list x 次,到达从位置x 开始的子列表。 从这个子列表中,它将剩余的单元格数量下降到位置 y。两个列表xlistylist 是分别从位置xy 开始的列表。 然后,我用ROTATEF交换他们的car

(defun nswap (list x y)
  (assert (< x y))
  (let* ((xlist (nthcdr x list))
         (ylist (nthcdr (- y x) xlist)))
    (prog1 list
      (rotatef (car xlist)
               (car ylist)))))

功能版

纯功能的swap 可以在(copy-list lst) 上调用nswap。 然而,也可以与lst 共享一些单元格,因为ylist 其余部分之后的所有内容都保持不变。 这就是函数的工作原理,它像以前一样计算xlistylist,但使用LDIFF 生成新列表(初始列表的区域)。 初始列表的不同切片与nconc 连接(它们是新列表,因此代码具有局部副作用,但在外部,输入列表未修改)。 (rest ylist)nconc 的最后一个参数,不是副本。

(defun swap (list x y)
  (assert (< x y))
  (let* ((xlist (nthcdr x list))
         (ylist (nthcdr (- y x) xlist)))
    (nconc (ldiff list xlist)
           (list (first ylist))
           (ldiff (rest xlist) ylist)
           (list (first xlist))
           (rest ylist))))

例如,将:circle t 用于write 让我们看到列表的共享末尾:

(let ((list '(a b c d e f g h i)))
  (write (list :original list
               :swapped (swap list 1 3))
         :circle t))

打印如下,#1= 标记尾部,#1# 表示结果中使用了相同的尾部:

(:ORIGINAL (A B C D . #1=(E F G H I))
 :SWAPPED (A D C B . #1#))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-14
    • 2018-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多