这是一个避免递归并使用一个不错的 Common Lisp 库的版本iterate
:
(ql:quickload :iterate)
(use-package :iterate)
(defun minmax (tree)
(iter
(with head := tree)
(with stack := nil)
(while (or head stack))
(cond
((and (null head) stack)
(setf head (cdr (pop stack))))
((consp (car head))
(push head stack)
(setf head (car head)))
((car head)
(minimize (car head) :into min)
(maximize (car head) :into max)
(setf head (cdr head))))
(finally (return (values min max)))))
对于非常大、深度嵌套的树,这将是一种首选方式,它以一些复杂性和 O(log n) 空间(用于回溯)来换取可伸缩性(此处建议的其他版本将无法在足够大的树上执行,其中限制将是 Lisp 实现为每个线程分配给此函数堆栈的内存。)
然而,递归版本的好处可能是原则上更容易使其并行运行多个计算。
然后使用一些宏观魔术,您可以隐藏原始minmax
函数的无趣位(那些只处理迭代树的位):
(defmacro-clause (FOR var IN-TREE tree)
"Iterates over TREE in depth-first order"
(let ((stack (gensym)) (head (gensym)))
`(progn
(with ,head := ,tree)
(with ,stack := ,nil)
(with ,var := ,nil)
(while (or ,head ,stack))
(cond
((and (null ,head) ,stack)
(setf ,head (cdr (pop ,stack)))
(next-iteration))
((consp (car ,head))
(push ,head ,stack)
(setf ,head (car ,head))
(next-iteration)))
(setf ,var (car ,head) ,head (cdr ,head)))))
(defun minmax (tree)
(iter
(for leaf :in-tree tree)
(minimize leaf :into min)
(maximize leaf :into max)
(finally (return (values min max)))))
并且具有更精简的功能,仅显示重要部分。
这是使用lparallel
库的算法的并行版本:
(ql:quickload :lparallel)
(setf lparallel:*kernel* (lparallel:make-kernel 128))
(lparallel:defpun pminmax (tree &optional min max)
(labels ((%min (&rest numbers)
(iter (for i :in numbers) (when (numberp i) (minimize i))))
(%max (&rest numbers)
(iter (for i :in numbers) (when (numberp i) (maximize i)))))
(cond
((null tree) (cons min max))
((consp (car tree))
(lparallel:plet
((head (pminmax (car tree) min max))
(tail (pminmax (cdr tree) min max)))
(cons (%min (car head) (car tail) min)
(%max (cdr head) (cdr tail) max))))
(t (destructuring-bind (new-min . new-max)
(pminmax (cdr tree) min max)
(cons (%min (car tree) new-min min)
(%max (car tree) new-max max)))))))
不幸的是,lparallel
还没有实现 for 的替代方案multiple-value-bind
,所以我们不得不将结果合并到一个 cons 单元格中,但是如果有一些持久性,就有可能实现上述宏的并行版本并摆脱这个不幸的限制(即作为练习留给读者)。