递归是一种功能遗产
递归是一个源自函数式风格的概念。将它与命令式风格混合起来会给新程序员带来很多痛苦和困惑。
To design a recursive function,我们确定了 base 和 inductive 情况。
- 基本情况 - 要求和的项目列表为空;即,
item 是Empty。返回0
- 归纳案例 1 - 项目列表非为空;即,必须至少有一个
item。如果项目是列表,则返回其总和加上项目的rest 的总和
- 归纳案例 2 - 至少有一个
item 不是数组。返回此项目加上项目的rest 的总和
const Empty =
Symbol ()
const sumDeep = ([ item = Empty, ...rest ] = []) =>
item === Empty
? 0
: Array.isArray (item)
? sumDeep (item) + sumDeep (rest)
: item + sumDeep (rest)
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
由于此实施,所有痛苦和痛苦都从程序中消失了。我们不关心局部状态变量、变量重新分配或像forEach 这样的副作用,也不使用函数调用的返回值。
递归注意
还有一个尾递归版本,可以制作为stack-safe。在这里,我们添加了一个参数cont 来表示我们的延续,它有效地允许我们在不增加堆栈的情况下对+ 操作的顺序进行排序 - bold
的变化
常量标识 = x =>
x
const sumDeep = ([ item = Empty, ...rest ] = [], cont = identity) =>
项目 === 空
? 续(0)
: Array.isArray (项目)
? sumDeep(项目,a =>
sumDeep(休息,b =>
续 (a + b)))
: sumDeep (休息, a =>
续(项目+ a))
用法相同
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
性能提升
正如@גלעד ברקן 指出的,上面使用的数组解构语法(例如...rest)会创建输入数组的副本。如他/她的回答所示,可以使用索引参数来避免创建副本。这种变化显示了索引技术也可以以尾递归的方式使用
const identity = x =>
x
const sumDeep = (items = [], i = 0, cont = identity) =>
i >= items.length
? cont (0)
: Array.isArray (items [i])
? sumDeep (items [i], 0, a =>
sumDeep (items, i + 1, b =>
cont (a + b)))
: sumDeep (items, i + 1, a =>
cont (items [i] + a))
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)