存储函数调用并执行它们

时间:2015-11-10 18:52:30

标签: lisp common-lisp

我编写了这些函数来混合给定列表:

(defun swap (lst pos1 pos2)
  "Destructively swap values of lst between positions pos1 and pos2"
  (let ((aux (nth pos1 lst)))
    (setf (nth pos1 lst) (nth pos2 lst))
    (setf (nth pos2 lst) aux)) lst)

(defun mix-function ()
  "Create function to mix values, and than revert changes"
  (let ((parameters '()))
    (lambda (lst)
      (let ((len (length lst)))
        (if (null parameters)
            (dotimes (i len)
              (push (list (random len) (random len)) parameters))
          (setf parameters (nreverse parameters)))
        (map nil #'(lambda (x) (apply #'swap (cons lst x))) parameters) lst))))

示例电话:

CG-USER(109): (defparameter fun (mix-function))
FUN
CG-USER(110): (defparameter val '(1 2 3 4 5))
VAL
CG-USER(111): (funcall fun val)
(5 2 1 4 3)
CG-USER(112): (funcall fun val)
(1 2 3 4 5)

是否可以通过存储函数调用来完成此操作?我该怎么办?到目前为止,在第一个funcall之后使用setf时,我的工作效果不佳:

(defun mix-function2 ()
  (let ((operations '()))
    (lambda (lst)
      (let ((len (length lst)))
        (if (null operations)
            (dotimes (i len)
              (push `(swap ',lst ,(random len) ,(random len)) operations))
          (setf operations (nreverse operations)))
        (map nil #'eval operations) lst))))

CG-USER(140): (defparameter fun2 (mix-function2))
FUN2
CG-USER(141): val
(1 2 3 4 5)
CG-USER(142): (funcall fun2 val)
(5 2 1 4 3)
CG-USER(143): (setf val '(1 2 3 4 5 6))
(1 2 3 4 5 6)
CG-USER(144): (funcall fun2 val)
(1 2 3 4 5 6)
CG-USER(145): (funcall fun2 val)
(1 2 3 4 5 6)

是否可以在没有评估的情况下编写?

1 个答案:

答案 0 :(得分:1)

首次尝试时,有一些事情有点奇怪。如果操作尚未填充,则返回一个函数,该函数将创建与调用函数的列表中的交换数量相同的交换。但是你可以在任何列表上调用该函数,即使它没有相同的长度。如果您使用长列表调用它,然后传入较短的列表,您将尝试交换列表实际上没有的位置。此外,您不需要写 swap ; Common Lisp已经提供了更通用的rotatef,它接受​​任意位置,例如 nth 。因此,我将此写为接受列表的函数,然后返回一个函数,该函数的连续调用返回该列表的混洗版本。避免您已经显示的 eval 用法的诀窍是收集 lambda 函数而不是代码blob,然后收集 funcall 它们。

(defun make-shuffler (list)
  (let* ((len (length list))
         (operations (loop repeat len
                          collect (let ((i (random len))
                                        (j (random len)))
                                    (lambda ()
                                      (rotatef (nth i list) (nth j list)))))))
    (lambda () 
      (map nil 'funcall operations)
      list)))
CL-USER> (defparameter *f* (make-shuffler (list 1 2 3 4 5 6)))
*F*
CL-USER> (funcall *f*)
(4 1 5 2 3 6)
CL-USER> (funcall *f*)
(2 4 3 1 5 6)
CL-USER> (funcall *f*)
(1 2 5 4 3 6)
CL-USER> (funcall *f*)
(4 1 3 2 5 6)
CL-USER> (funcall *f*)
(2 4 5 1 3 6)
CL-USER> (funcall *f*)
(1 2 3 4 5 6)

当然,有很多选项可用于创建交换功能列表。但要注意使用捕获循环变量,因为它们可能不会在每次迭代时反弹。 (这就是我在收集子句中使用 let 的原因。)但您也可以只收集随机索引,然后调用单个交换函数。这可能会更有效率(但我还没有检查过):

 (defun make-shuffler (list)
  (let* ((len (length list))
         (index-pairs (loop repeat len
                         collect (list (random len) (random len))))
         (swap (lambda (i j)
                 (rotatef (nth i list) (nth j list)))))
    (lambda ()
      (map nil (lambda (indices)
                 (apply swap indices))
           index-pairs)
      list)))

另外,在回复您的评论时,请注意 make-shuffler 函数返回的列表始终是相同的列表,只是其元素的顺序不同。这意味着您可以在调用洗牌函数之间修改列表,并且您将看到反映的更改(但这是有意义的)。例如:

(let* ((list (list 1 2 3 4 5))
       (f (make-shuffler list)))
  (funcall f)
  (print list)
  (setf (second list) 'x)
  (setf (third list) 'y)
  (print list)
  (funcall f)
  (print list))

; (3 4 2 1 5) ; after first shuffle
; (3 X Y 1 5) ; after setting X and Y, but without shuffling
; (Y 1 X 3 5) ; after another shuffle, X and Y are still in the list