用于mapcar的lambda操作的Lisp顺序

时间:2014-01-03 20:09:43

标签: lisp common-lisp

我正在Linux中运行clisp,并使用本书中的练习 ANSI Common Lisp 。其中一个人说使用mapcar创建一个函数,该函数获取整数列表并返回一个列表,其中每个元素都是原始元素加上列表中的索引。因此,如果我执行(foo '(0 0 0)),它将生成(0 1 2)等等。

我试过的是:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (+ x c) (incf c)) lst)))

我运行(foo '(0 0 0))时得到的是(1 2 3)。就像一个实验,我交换了增量的顺序并以这种方式定义:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (incf c) (+ x c)) lst)))

我得到了完全相同的结果(在这种情况下,我希望(1 2 3),但不是前一种情况)。我也尝试像这样(progn (+ x c) (incf c))包装序列并获得相同的结果。为了使函数有效,我需要(let ((c -1)))。为什么(incf c)(+ x c)的顺序在这种情况下似乎不重要?看来(incf c)总是先行,不过,但我不确定为什么。我知道我在这里缺少一些基本的东西,所以这应该是LISP专家的一个快速简单的答案,向我解释为什么这样做。 :)

感谢。

3 个答案:

答案 0 :(得分:3)

在第一个示例中,lambda函数的结果是最后一个表单(incf c)的结果。 (+ x c)被忽略。

在你的第二个例子中,你先增加c,所以1加到第一个数字,2加到第二个,等等。

对于您的第一个示例,您还可以使用prog1返回第一个表单的值,而不是最后一个:

(defun foo (lst)
  (let ((c 0))
    (mapcar
     (lambda (e) (prog1 (+ e c) (incf c)))
     lst)))

答案 1 :(得分:3)

注意:这是为了探索实现这一目标的一些方法。你在你的问题中注意到了一些解决方案(例如,从-1开始),并不意味着将它们包含在这里以表明你没有考虑过它们。这个答案更多的是考虑增值前和增量后算子在价值导向语言中的作用。

请注意,(incf c)不仅会执行副作用(顺序无关紧要),还会返回新值c。也就是说,incf是一个预增量运算符。这意味着写(+ x (incf c))绝对可以接受,如:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (1 2 3)

现在,这些结果并不可怕,但是他们没有将元素的索引添加到元素中,因为索引从零开始,而不是一个。您可以进行简单的更改并添加减法:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (1- (incf c)))) ; or (+ x -1 (incf c)), etc.
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

在我看来,甚至比这更好的方法是使用第一种形式,然后在c开始-1。无论如何,这是我可能做的,你在问题中注意到这是一个选择:

(defun foo (lst)
  (let ((c -1))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

我们可能会问,是否有一种近似后增量算子的方法,这样我们就不必看到(在我们的代码中)减法?也就是说,我们可能会将上述内容视为我们在某些语言中所拥有的内容:

result.add( x + ++c )

并问我们为什么不能做

result.add( x + c++ ) 

代替?这很简单:

(defmacro post-incf (place)
  `(1- (incf ,place)))

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (post-incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

实际上,C语言的增量前和增量后运算符没有灵活性,但Common Lisp中的incf and decf通过使用可选参数提供了更多的功能,其中第二个可选参数可用于指定值应该改变多少。调整post-incf以处理这样的第二个参数并不太难。第一次尝试可能是:

(defmacro post-incf (place &optional (delta-form 1))
  `(- (incf ,place ,delta-form) ,delta-form))

这对1等常量值很有效,但它会多次评估delta-form,而我们也不想这样(因为它可能有副作用,或者计算费用昂贵)。我们只需要更加谨慎地评估它一次,因此:

(defmacro post-incf (place &optional (delta-form 1))
  (let ((delta (gensym (symbol-name '#:delta-))))
    `(let ((,delta ,delta-form))
       (- (incf ,place ,delta) ,delta))))

(let ((a 0))
  (list a                ; => 0
        (incf a 3)       ; => 3
        (post-incf a 2)  ; => 3 (but x is now 5)
        a))              ; => 5
;=> (0 3 3 5)

答案 2 :(得分:1)

在你的第一个lambda (lambda (x) (+ x c) (incf c))中,你正在执行一个无副作用的添加并丢掉它的值。然后递增c并返回lambda的值作为新值。

尝试调用(foo '(10 10 10))并查看返回值是什么,这两个版本将返回(1 2 3)(11 12 13)