以下是来自 Lisp的土地(第138页或此处:http://landoflisp.com/wumpus.lisp)的一段代码:
(defun get-connected (node edge-list)
(let ((visited nil))
(labels ((traverse (node)
(unless (member node visited)
(push node visited)
(mapc (lambda (edge)
(traverse (cdr edge)))
(direct-edges node edge-list)))))
(traverse node))
visited))
作为一个新手,我想知道第9行的(遍历节点)是否在“包装”函数中最终调用了这个“本地”定义的函数 get-connected 即可。也就是说,当调用 get-connected 时,它会“构造”遍历(第3 - 8行),然后在第9行调用/使用它。此外,本地变量已访问似乎被遍历的推送命令打包,然后留在 let 块的后门口我猜,作为“回报价值?”当然,这必须是某种方式的返回值,“实时”并且可用于调用函数;否则,整个连接功能都没有用!
对我来说,一个Lisp /功能新手,这一切看起来都很奇怪。这是我听过的关闭野兽之一吗?如果是这样,我通过这样一个奇怪的构造函数获得了什么?我理解它的作用 - 它从图形顶点开始并遍历尽可能多的边缘,记住顶点,但它(除了漂亮的递归之外)是典型的Lisp /功能策略吗?
BTW,这是一个边缘列表的例子:
*test-edges* = ((2 . 8) (8 . 2) (4 . 5) (5 . 4) (6 . 2) (2 . 6) (3 . 10) (10 . 3) (7 . 5)
(5 . 7) (7 . 10) (10 . 7) (4 . 5) (5 . 4) (9 . 3) (3 . 9) (3 . 5) (5 . 3)
(3 . 1) (1 . 3) (5 . 8) (8 . 5) (8 . 9) (9 . 8) (5 . 2) (2 . 5) (1 . 2)
(2 . 1))
和节点只是这些数字中的一个,这里是1到10之间的整数。这里有一些输出:
CL-USER> (get-connected 1 *test-edges*)
(9 6 2 8 4 5 7 10 3 1)
答案 0 :(得分:2)
让我们改进缩进:
(defun get-connected (node edge-list)
(let ((visited nil))
(labels ((traverse (node)
(unless (member node visited)
(push node visited)
(mapc (lambda (edge)
(traverse (cdr edge)))
(direct-edges node edge-list)))))
(traverse node))
visited))
该功能并不奇怪。它基本上是一个设置一些变量的函数,定义一个局部自递归函数,然后调用一次本地函数。
闭包怎么样?闭包是一个带有环境的函数对象。我们构造函数对象的唯一地方是MAPC
形式的lambda。否则,不会创建和传递/返回任何函数对象。传递给MAPC
的函数对象是第一个参数,是一个闭包。
我们也可以把它写成两个全局函数。本地函数方法的优点是什么:
本地函数名称不是全局已知的。由于我们只在本地使用它,因此没有必要将其全局化。
我们不需要将所有信息传递给本地函数。它可以访问周围的变量。
我们可以有一个共享的词汇变量,用于收集结果
我们可以把它写成两个全局函数。它可能看起来像这样:
现在,遍历是一个全局函数,我们需要扩展边缘列表和被访问节点列表的参数列表(到目前为止)。我们还需要构造一个返回值:访问过的节点。
(defun traverse (node edge-list visited)
(if (member node visited)
visited
(progn
(mapc (lambda (edge)
(setf visited (traverse (cdr edge) edge-list (cons node visited))))
(direct-edges node edge-list))
(cons node (remove node visited)))))
您在函数式编程中看到的典型模式也可以通过某种形式的递归替换MAPC。
现在这是我们新的全球功能:
(defun get-connected-1 (node edge-list)
(traverse node edge-list nil))
它传递变量并添加一个空的受访节点列表。
回到原来的功能:
(defun get-connected (node edge-list)
(let ((visited nil)) ; a shared local variable
(labels ((traverse (node) ; a local self-recursive function
(unless (member node visited)
(push node visited) ; update the shared local variable
(mapc (lambda (edge) ; loop over the direct edges
(traverse (cdr edge))) ; traverse the connected
(direct-edges node edge-list)))))
(traverse node)) ; call the local function from get-connected
visited)) ; get-connected returns this as the return value
上面的局部遍历函数有什么不好?它可能导致某些较大图形上的堆栈溢出。人们需要将其重写为尾递归变体。
我们也可以摆脱本地功能,自己管理已访问和未访问过的节点列表:
(defun get-connected-2 (start-node edge-list)
(let ((visited nil)
(nodes-to-visit (list start-node)))
(loop for node = (pop nodes-to-visit)
while node do
(unless (member node visited)
(push node visited)
(loop for (nil . to) in (direct-edges node edge-list)
do (pushnew to nodes-to-visit))))
visited))