我正在用Lisp开发一个k-d树。我正在编写一个函数,允许我搜索k-d树中的节点。该功能定义如下:
(defmethod find-node ((kdt kdtree) target &key (key #'value) (test #'equal))
(unless (null (root kdt))
(find-node (root kdt) target :key key :test test)))
(defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
(format t "Testing node ~a~%" (value node))
(format t "Result is ~a~%" (funcall test (funcall key node) target))
(if (funcall test (funcall key node) target)
node
(progn
(unless (null (left node))
(find-node (left node) target :key key :test test))
(unless (null (right node))
(find-node (right node) target :key key :test test)))))
我使用以下数据构建了一个树:'((2 3) (5 4) (9 6) (4 7) (8 1) (7 2))
。所以现在,我正在使用此函数来查找节点'(2 3)
。
(find-node kdt '(2 3))
使用format
语句,我得到了这个输出:
Testing node (7 2)
Result is NIL
Testing node (5 4)
Result is NIL
Testing node (2 3)
Result is T
Testing node (4 7)
Result is NIL
Testing node (9 6)
Result is NIL
Testing node (8 1)
Result is NIL
NIL
因此,正如您所看到的,由于测试结果为T
,因此找到了节点,但搜索仍在继续,结果为NIL
。为什么这个函数没有返回节点?
答案 0 :(得分:4)
当您找到某些内容时,您可能希望直接返回结果。
使用以下示例改进您的代码:
(defun find-it (item list)
(labels ((find-it-aux (item list)
(when list
(if (eql item (first list))
(return-from find-it (first list))
(find-it-aux item (rest list))))))
(find-it-aux item list)))
CL-USER 89 > (find-it 1 '(2 3 1 5))
1
CL-USER 90 > (find-it 1 '(2 3 5))
NIL
或
(defun find-it (item list)
(catch 'found-it
(find-it-aux item list)))
(defun find-it-aux (item list)
(when list
(if (eql item (first list))
(throw 'found-it (first list))
(find-it-aux item (rest list)))))
CL-USER 91 > (find-it 1 '(2 3 1 5))
1
CL-USER 92 > (find-it 1 '(2 3 5))
NIL
答案 1 :(得分:2)
您可以稍微简化一下,并在找到项目后停止搜索,方法是为nil
个节点定义并使用or
:
(defmethod find-node ((empty null) target &key &allow-other-keys)
nil)
(defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
(if (funcall test (funcall key node) target)
node
(or (find-node (left node) target :key key :test test)
(find-node (right node) target :key key :test test))))
当然,你可以将这与Rainer Joswig的答案结合起来。例如,您可以为:around
定义kdnode
方法:
(defvar *searching* nil)
(defmethod find-node :around ((x kdnode) target &key &allow-other-keys)
(if *searching*
(let ((result (call-next-method)))
(when result
(throw 'found result)))
(let ((*searching* t))
(catch 'found
(call-next-method)))))
您还可以在代码中明确放置catch
块。如果您确定永远不会在kdnode
但始终在kdtree
个实例上发起搜索,那么您可以将catch
放在kdtree
周围,然后摆脱特殊变量*searching*
。
请注意,此示例仅用于演示方法。它使代码变得有点过于聪明&#34 ;;我可能会在实践中明确地实现throw / catch行为。
答案 2 :(得分:0)
该函数继续测试节点,因为它是递归的。如果向左下方(find-node (left node) ...
),则将右侧分支中的搜索放在堆栈上(find-node (right node) ...
)。因此,正在测试的节点都是因为递归。解决这个问题的一种方法是重写这样的函数:
(defmethod find-node ((node kdnode) target &key (key #'value) (test #'equal))
(let ((left (unless (null (left node))
(find-node (left node) target :key key :test test)))
(right (unless (null (right node))
(find-node (right node) target :key key :test test)))
(this (funcall test (funcall key node) target)))
(if this
node
(if left
left
right))))