使用以下规范在LISP中编写Foo函数

使用以下规范在LISP中编写Foo函数,lisp,common-lisp,Lisp,Common Lisp,我正在努力找到解决以下函数的正确方法 (FOO #'– '(1 2 3 4 5)) => ((–1 2 3 4 5) (1 –2 3 4 5) (1 2 –3 4 5) (1 2 3 –4 5) (1 2 3 4 –5)) foo函数的第一个参数应该是一个函数,必须应用于返回列表列表的每个元素,如上所示。我不确定我可以采取什么方法来创建这个函数。我考虑过递归,但不确定如何在每次调用中保留列表,以及我会有什么样的基本标准。任何帮助都将不胜感激。我不能使用循环,因为这是函数式编程。递归的基

我正在努力找到解决以下函数的正确方法

(FOO #'– '(1 2 3 4 5))
=> ((–1 2 3 4 5) (1 –2 3 4 5) (1 2 –3 4 5) (1 2 3 –4 5) (1 2 3 4 –5)) 

foo函数的第一个参数应该是一个函数,必须应用于返回列表列表的每个元素,如上所示。我不确定我可以采取什么方法来创建这个函数。我考虑过递归,但不确定如何在每次调用中保留列表,以及我会有什么样的基本标准。任何帮助都将不胜感激。我不能使用循环,因为这是函数式编程。

递归的基本情况可以通过问自己何时停止来确定

举个例子,当我想计算一个整数和它下面的所有正整数之和时,我可以通过回答“我想什么时候停止”来确定一个基本情况来重复计算?当我可能加进去的值为零时:

(defun sumdown (val)
  (if (zerop val)
      0
      (+ (sumdown (1- val)) val)))
关于“在每次通话中保留列表”,我不会试图保留任何内容,而是在您继续进行时建立一个结果。使用“sumdown”示例,这可以通过各种基本相同的方法来实现

这种方法是使用一个辅助函数和一个调用辅助函数的函数,该函数带有一个result参数,可以在递归时生成一个结果:

(defun sumdown1-aux (val result)
  (if (zerop val)
      result
      (sumdown1-aux (1- val) (+ val result))))

(defun sumdown1 (val)
  (sumdown1-aux val 0))
通过使用可选参数,可以组合辅助函数和用户要调用的函数:

(defun sumdown2 (val &optional (result 0))
  (if (zerop val)
      result
      (sumdown2 (1- val) (+ val result))))
通过在用户将调用的函数中本地绑定辅助函数,可以隐藏正在使用该函数的事实:

(defun sumdown3 (val)
  (labels ((sumdown3-aux (val result)
             (if (zerop val)
                 result
                 (sumdown3-aux (1- val) (+ val result)))))
    (sumdown3-aux val 0)))

可以通过回答以下问题来实现问题的递归解决方案:当我要对列表中的每个元素进行操作时,何时停止?以确定基本情况,并建立列表的结果列表,而不是像示例中那样在递归时添加。将问题分解为更小的部分将有助于-复制原始列表,并将第n个元素替换为对该元素调用函数的结果,可以将其视为一个子问题,因此您可能希望先编写一个这样做的函数,然后使用该函数编写一个解决整个问题的函数。如果允许您使用mapcar和substitute或substitute if等函数,则会更容易,但如果您不允许,则可以使用允许使用的函数编写等价物。

遗憾的是,您不能使用loop,因为这可以像这样优雅地解决:

(defun foo (fctn lst)
  (loop 
     for n from 0 below (length lst)  ; outer
     collect (loop 
                for elt in lst        ; inner
                for i from 0 
                collect (if (= i n) (funcall fctn elt) elt))))
所以我们有一个外循环,它将n从0增加到长度lst excluded,还有一个内循环,它将逐字复制列表,除了应用fctn的元素n之外:

CL-USER> (foo #'- '(1 2 3 4 5))
((-1 2 3 4 5) (1 -2 3 4 5) (1 2 -3 4 5) (1 2 3 -4 5) (1 2 3 4 -5))
以递归替换循环意味着通过使用替换内部和外部循环的标签来创建局部函数,例如:

(defun foo (fctn lst)
  (let ((len (length lst)))
    (labels
        ((inner (lst n &optional (i 0))
           (unless (= i len)
             (cons (if (= i n) (funcall fctn (car lst)) (car lst))
                   (inner (cdr lst) n (1+ i)))))
         (outer (&optional (i 0))
           (unless (= i len)
             (cons (inner lst i) (outer (1+ i))))))
      (outer))))

您在这里选择的部分实现策略将取决于您是否希望支持结构共享。一些答案提供了解决方案,您可以获得全新的列表,这可能是您想要的。如果您想要实际共享一些公共结构,您也可以使用这样的解决方案来实现。注意:我使用first/rest/list*而不是car/car/cons,因为我们使用的是列表,而不是任意的树

(defun foo (operation list)
  (labels ((foo% (left right result)
             (if (endp right) 
                 (nreverse result)
                 (let* ((x (first right))
                        (ox (funcall operation x)))
                   (foo% (list* x left)
                         (rest right)
                         (list* (revappend left
                                           (list* ox (rest right)))
                                result))))))
    (foo% '() list '())))
我们的想法是沿着列表向下走一次,以相反的方式跟踪左侧和右侧,这样我们就得到了左侧和右侧:

在每一步(最后一步除外)中,我们从右侧获取第一个元素,应用该操作,并创建一个新的列表,用于左侧、操作结果和右侧的其余部分。所有这些操作的结果按相反顺序累积。最后,我们只返回结果,相反。我们可以检查结果是否正确,同时观察结构共享:

CL-USER> (foo '- '(1 2 3 4 5))
((-1 2 3 4 5) (1 -2 3 4 5) (1 2 -3 4 5) (1 2 3 -4 5) (1 2 3 4 -5))
CL-USER> (setf *print-circle* t)
T

CL-USER> (let ((l '(1 2 3 4 5)))
           (list l (foo '- l)))
((1 . #1=(2 . #2=(3 . #3=(4 . #4=(5)))))   ; input L
 ((-1 . #1#)
  (1 -2 . #2#)
  (1 2 -3 . #3#)
  (1 2 3 -4 . #4#)
  (1 2 3 4 -5)))
通过将*print circle*设置为true,我们可以看到结构共享:

CL-USER> (foo '- '(1 2 3 4 5))
((-1 2 3 4 5) (1 -2 3 4 5) (1 2 -3 4 5) (1 2 3 -4 5) (1 2 3 4 -5))
CL-USER> (setf *print-circle* t)
T

CL-USER> (let ((l '(1 2 3 4 5)))
           (list l (foo '- l)))
((1 . #1=(2 . #2=(3 . #3=(4 . #4=(5)))))   ; input L
 ((-1 . #1#)
  (1 -2 . #2#)
  (1 2 -3 . #3#)
  (1 2 3 -4 . #4#)
  (1 2 3 4 -5)))
输出中的每个列表与原始输入列表共享尽可能多的结构

从概念上讲,我发现使用标签递归地编写这类函数更容易,但Common Lisp不能保证尾部调用优化,因此值得以迭代方式编写。这里有一种方法可以做到:

(defun phoo (operation list)
  (do ((left '())
       (right list)
       (result '()))
      ((endp right)
       (nreverse result))
    (let* ((x (pop right))
           (ox (funcall operation x)))
      (push (revappend left (list* ox right)) result)
      (push x left))))

非常感谢你这个答案真的很有帮助。我能够使用替代方法,我使用了使用辅助方法的尾部递归方法,并且它起了作用。我还可以使用mapcar和substitute,所以这是一个很大的帮助。@HeartbeatDeveloper很高兴听到它的帮助。请注意,使用“替换”意味着您可能会替换多个值。例如,foo'12321将导致-1232-111-233-2112-3211-2331-231-231-231-231-231-1232-1。这可能满足您的需求,也可能不满足您的需求,确保只有一个元素具有调用的函数并不是太多额外的逻辑。