这是您的代码,稍微重新格式化:
(defun depth (value)
(if (or (null value) (atom value))
0
(+ 1 (apply 'max (mapcar #'depth value)))))
我将lst(顺便说一下,你可以写成list)重命名为value,因为这个名字让人困惑,因为它暗示变量总是一个列表,这是不正确的。函数depth 可以在任何值上调用:
(depth "hello")
=> 0
(depth 100)
=> 0
if 的 then 分支在 value 为 NIL 或任何 atom 时进行评估。由于NIL 也是atom,因此测试表达式可以简化为(atom value)。当值是原子时,深度为零。
if 的 else 分支在value 不是原子时进行评估,其中atom 的by definition 表示value 这里是cons。该函数还假定它是一个正确的列表,而不是某个循环列表。
由于value 是该分支中的一个列表,我们可以在其上调用mapcar:(mapcar #'depth value);这是函数假定列表正确的地方。
这将为value 中的每个v 计算(depth v)。更准确地说,如果value 是一个长度为n 的列表,那么对mapcar 的调用计算为一个数字列表(D1 ... Dn),其中Di 是(depth Vi) 之间的所有i 1 和n。
所以我们知道(apply 'max (mapcar ...)) 是(apply 'max depths) 对于一些数字列表depths。一般来说:
(apply fn v1 ... vn list)
... 是一种调用由fn 表达式表示的函数对象的方法,其中至少包含n 元素(v1 到vn),以及存储在@ 中的任意数量的附加元素987654362@。当您引用函数时,如 'max 或编写 #'max 时,您在函数命名空间中通过函数名称来引用函数。
将此与调用函数的通常方式进行对比:
(f x y z)
函数名称和传递的参数数量是固定的:只要读取表单,我们就知道有一个带有 3 个参数的 f 调用。
apply 函数是一个内置函数,允许您在最后一个调用参数中传递列表中的其他参数。上面的调用可以写成:
(apply #'f x y z ()) ;; note the empty list as a last argument
也可以这样写:
(apply #'f (list x y z)) ;; all arguments in the last list
唯一的区别可能是运行时效率的问题(并且使用好的编译器,可能没有区别)。
在你的例子中,你这样做:
(apply max depths)
这和写(伪代码)一样:
(max d1 d2 d3 ... dn)
... 其中depths 是列表(list d1 d2 ... dn)。
但是我们不能直接把它们全部写出来,因为列表的内容只有在运行时才知道。
因此,对应用的调用计算递归计算的所有深度中的最大深度。请注意,上面对apply 的使用有些不当,因为不应使用任意大小的列表调用apply:在名为CALL-ARGUMENTS-LIMIT 的标准中有一个限制,理论上允许低至50 ,这样一个列表的最大大小(我们将在下面看到一个替代方案)。
最后,depth 会根据这个结果评估 (+ 1 ...)。换句话说,整个表达式可以概括为:列表的深度是其所有元素的最大深度加1。
使用reduce
您可以使用REDUCE 在列表上连续计算max,而不是apply。这比apply 更可取,因为:
-
元素数量没有限制,比如apply
(reduce 'max depths) ;; works the same, but more reliably
-
不需要建立一个中间的深度列表,你可以遍历值列表,调用depth 并直接使用结果来计算最大值。骨架是:
(reduce (lambda (max-so-far item) ...)
value
:initial-value 0)
声明式方法
代替reduce,loop 宏可以用作表达相同计算的更易读的替代方案。我还使用了typecase,我认为这使意图更清晰:
(defun depth (value)
(typecase value
(atom 0)
(cons (1+ (loop for v in value maximize (depth v))))))