LISP:如何获得列表的运行总和? (没有全局变量)

时间:2013-03-12 23:05:35

标签: lisp common-lisp

我是一名LISP新手。

为了获得列表的运行总和,我写的是 -

(setf sum 0.0)
(mapcar #'(lambda(x)
    (setf sum (+ sum x)) sum) values))

例如,如果您输入'(1 2 3 4)作为输入,则上面的代码会返回'(1 3 6 10)作为输出,等等。

是否可以在不使用全局变量sum的情况下(以更优雅的方式)执行相同的操作?

5 个答案:

答案 0 :(得分:6)

(loop for x in '(1 2 3 4) sum x into y collect y)

scanl是一个oneliner:

(defun scanl (f init xs)
  (loop for x in xs collect (setf init (funcall f init x))))

答案 1 :(得分:4)

您可以使用loop,如下所示:

(defun running-sum (xs)
  (loop with sum = 0
        for x in xs
        collect (setf sum (+ sum x))))

(running-sum '(1 2 3 4))

它基本上是相同的,但它使用局部变量而不是全局变量,并且可能更清楚。

或者,您可以定义递归函数和包装函数:

(defun running-sum-recursive (xs)
  (running-sum-recursive2 0 xs))

(defun running-sum-recursive2 (sum xs)
  (if (eq xs nil)
    nil
    (let ((new-sum (+ sum (car xs))))
      (cons new-sum (running-sum-recursive2 new-sum (cdr xs))))))

(running-sum-recursive '(1 2 3 4))

然而,当loop可用时,这对我来说似乎很复杂。

请注意,在Haskell中,您可以像这样执行运行总和:

runningSum xs = scanl1 (+) xs

runningSum [1, 2, 3, 4]

这里的关键是scanl1 function。 Lisp中可能存在类似的东西(现在我们已经差不多写了两次),但我有一段时间没有使用过Lisp。

编辑:经过一些搜索,我认为Common Lisp不包含任何类似scanlscanl1的内容,所以这里是:

(defun scanl (f val xs)
  (loop for x in xs
        collect (setf val (funcall f val x))))

(defun scanl1 (f xs)
  (cons (car xs)
        (scanl f (car xs) (cdr xs))))

(scanl1 #'+ '(1 2 3 4))

编辑:感谢怀远回答有关如何缩短循环的建议。

答案 2 :(得分:3)

或者您可以使用高阶函数

(define (running-sum ls)
     (cdr (reverse (foldl (lambda (y xs) (cons (+ (car xs) y) xs)) '(0) ls))))

答案 3 :(得分:2)

Haskell确实有很多用于列表递归的函数库,但我们至少得到reduce。这是一个基本的(即没有loop魔法)功能解决方案:

(defun running-sum (lst)
  (reverse (reduce (lambda (acc x)
                     (cons (+ (first acc) x) acc))
                   (rest lst)
                   :initial-value (list (first lst)))))

我正在使用原始列表的头部作为初始值,并遍历列表的其余部分,在头部添加总和(因为在头部添加是很自然的),最后反转这样获得的列表。

在大多数情况下,当需要遍历累积值的序列时,可以使用reduce

这是使用push - nreverse成语的基本迭代解决方案:

(defun running-sum (lst)
  (let ((sums (list (first lst))))
    (dolist (x (rest lst))
      (push (+ x (first sums)) sums))
    (nreverse sums)))

答案 4 :(得分:1)

在Scheme I中,我将使用累加器递归地计算列表的总和。像这样:

; Computes a list of intermediary results of list summation
(define list-sum
  (lambda (l)
    (letrec ((recsum (lambda (lst acc acclst)
      (if (pair? lst)
        (recsum (cdr lst) (+ acc (car lst)) (cons acc acclst))
        (cons acc acclst)))))
    (recsum (cdr l) (car l) '()))))

输出:

> (list-sum '(1 2 3 4))   
(10 6 3 1)
> (list-sum '(2 4 6 8 10))
(30 20 12 6 2)
> 

递归列表的技巧是每次关闭第一个元素/ car并传递其余的/ cdr。您可以使用额外的参数(称为累加器)来保留中间结果,并传递总和。我上面使用了两个累加器:一个用于最后一个和一个用于所有先前总和的列表。

我从来没有在LISP中做过任何事情,所以我不知道这是否会直接翻译成你的方言(?),但它在概念上很简单,我相信它在LISP中也是可行的。

请问是否有些事情没有立即清楚。自从我使用这一系列语言已经有一段时间了:))