Skip to content

LISP - 在列表上使用 `reduce`

reduce 是 Common Lisp 中用于处理序列的强大高阶函数。它通过反复将给定函数应用于序列的元素,将序列(如列表或向量)“规约”为一个单个值。这种模式在其他编程语言中也称为“折叠”(fold)或“累加”(accumulate)。

(reduce function sequence &key from-end start end initial-value key)

其中:

  • function:一个函数(或一个命名函数的符号),它接受两个参数:累加结果和下一个元素。
  • sequence:要处理的列表、向量或其他序列。
  • :initial-value (可选):规约的起始值。如果未提供,则使用序列的第一个(或前几个)元素作为起始。
  • :from-end (可选):如果为真 (t),规约将从最右侧元素开始,而不是从左侧。
  • :key (可选):一个函数,在每个元素传递给规约 function 之前对其应用。

reduce 最经典的用法是求数字列表的和。

(print (reduce #'+ '(1 2 3 4)))
;; 工作原理:
; 1. (+ 1 2) -> 3
; 2. (+ 3 3) -> 6
; 3. (+ 6 4) -> 10
10

你可以使用任何接受两个参数的函数,例如 max。

(print (reduce #'max '(1 12 3 41 25)))
;; 工作原理:
; 1. (max 1 12) -> 12
; 2. (max 12 3) -> 12
; 3. (max 12 41) -> 41
; 4. (max 41 25) -> 41
41

initial-value 为累加提供了起始点。这在规约空列表时至关重要,否则会引发错误。

;; 求和从 100 开始,而不是从第一个元素开始。
(print (reduce #'+ '(1 2 3 4) :initial-value 100))
;; 如果列表为空,则返回初始值。
(print (reduce #'+ '() :initial-value 0))
110
0

对于非结合(non-associative)函数(如减法),顺序很重要。:from-end 会反转操作顺序。

;; 从左到右(默认):((10 - 5) - 2) -> 3
(print (reduce #'- '(10 5 2)))
;; 从右到左:(10 - (5 - 2)) -> 7
(print (reduce #'- '(10 5 2) :from-end t))
3
7

你可以提供一个自定义的匿名函数 (lambda) 来进行更复杂的规约,例如查找列表中最长字符串的长度。

(let ((words '("lisp" "is" "powerful")))
(print
(reduce (lambda (longest-len current-word)
(max longest-len (length current-word)))
words
:initial-value 0)))
8
  • 在空列表上使用 reduce:在空序列上调用 reduce 且不提供 :initial-value 将会报错。如果序列可能为空,请务必提供 :initial-value。
  • reduce 与 loop:reduce 非常适合简洁、函数式风格的单值累加。对于涉及副作用、多个累加器或复杂终止条件的更复杂迭代,loop 宏通常更具可读性和强大性。