如何在您明确什么需要完成后,将更容易实现该功能。
输入
您的输入是一个列表,但不是任何类型的列表。
让我们将这种特定类型的列表命名为 node。
一个节点要么是一个空列表,要么是一个由3个元素组成的列表(v n1 n2),其中:
-
v 是一个数字,
-
n1 (resp. n2) 要么是一个节点 要么是一个数字。
输出
当您在节点上调用rec时,它应该输出一个数字或nil。
大纲
让我们定义一个辅助num 函数,它接受一个数字或一个节点并返回一个数字或零,通过调用rec:
-
(num n) 的数字 n 应该返回 n
-
(num n) 对于节点 n 应该返回 (rec n)
那么,rec 可以定义如下:
-
(rec nil) 应该是 nil
-
当
(num n1) 和(num n2) 是相等的数字时,(rec (v n1 n2)) 应该返回(+ v (num n1) (num n2))。在任何其他情况下,rec 应返回 nil。
示例
以下是一种可能的实现方式,它依赖于本地函数定义 (FLET) 等运算符、基于值类型 (TYPECASE) 的切换和 提前返回 技术。另见DESTRUCTURING-BIND。使用逻辑运算符(和/或)对于组合可能的 NIL 中间结果很有用:
(defun rec (node)
(flet ((num-or-fail (n)
(or (typecase n
(number n)
(cons (rec n)))
(return-from rec))))
(and node
(destructuring-bind (v n1 n2) node
(let ((v1 (num-or-fail n1))
(v2 (num-or-fail n2)))
(and (= v1 v2)
(+ v v1 v2)))))))
在 REPL(命令行)中,您可以为 rec 激活跟踪:
CL-USER> (trace rec)
然后你就可以测试了:
CL-USER> (rec '(10 (12 (5 2 2) 9) (2 14 (4 (3 1 1) 5))))
上面返回 70 并在 SBCL 中打印以下跟踪:
0: (REC (10 (12 (5 2 2) 9) (2 14 (4 (3 1 1) 5))))
1: (REC (12 (5 2 2) 9))
2: (REC (5 2 2))
2: REC returned 9
1: REC returned 30
1: (REC (2 14 (4 (3 1 1) 5)))
2: (REC (4 (3 1 1) 5))
3: (REC (3 1 1))
3: REC returned 5
2: REC returned 14
1: REC returned 30
0: REC returned 70
Early return 甚至可以从最外层的调用中逃脱,因为它意味着整个结果为 NIL(有点像异常)。您也可以将 rec 设为本地函数,与 num-or-fail 相互递归,并以不同的方式命名您的主函数:
(defun sumtree (node)
(labels ((num-or-fail (n)
(or (typecase n
(number n)
(cons (rec n)))
(return-from sumtree)))
(rec (node)
(and node
(destructuring-bind (v n1 n2) node
(let ((v1 (num-or-fail n1))
(v2 (num-or-fail n2)))
(and (= v1 v2)
(+ v v1 v2)))))))
(rec node)))
上面,当中间结果之一是nil时,return-from展开整个递归调用堆栈并直接返回nil。