有时处理一个稍微更一般的问题会更容易一些,然后弄清楚如何将其专门用于手头的特定问题。在这种情况下,您将获得某种结构,以及一些可以访问该结构的子结构的访问器。给定一个要查找的元素和要搜索的事物,您可以通过检查该事物是否是该元素进行搜索,如果是,则返回到目前为止的路径(以适当的格式),如果不是,那么如果它是您可以使用访问器分解的结构,尝试每个分解的部分。
(defun find-element (element structure structure-p accessors &key (test 'eql))
(labels ((fe (thing path)
"If THING and ELEMENT are the same (under TEST), then
return PATH. Otherwise, if THING is a structure (as
checked with STRUCTURE-P), then iterate through
ACCESSORS and recurse on the result of each one
applied to THING."
(if (funcall test thing element)
;; return from the top level FIND-ELEMENT
;; call, not just from FE.
(return-from find-element path)
;; When THING is a structure, see what
;; each of the ACCESSORS returns, and
;; make a recursive call with it.
(when (funcall structure-p thing)
(dolist (accessor accessors)
(fe (funcall accessor thing)
(list* accessor path)))))))
;; Call the helper function
;; with an initial empty path
(fe structure '())))
这将返回我们需要的访问器序列,与它们需要应用于结构的相反顺序。例如:
(find-element 'waldo '(ralph waldo emerson) 'consp '(car cdr))
;=> (CAR CDR)
因为(car (cdr '(ralph waldo emerson)))
是waldo
。相似地
(find-element 'emerson '(ralph (waldo emerson)) 'consp '(first rest))
;=> (FIRST REST FIRST REST)
因为(first (rest (first (rest '(ralph (waldo emerson))))))
是emerson
。所以我们已经解决了获取访问器函数列表的问题。现在我们需要构建实际的表达式。这实际上是一个相当简单的任务,使用reduce
:
(defun build-expression (accessor-path structure)
(reduce 'list accessor-path
:initial-value (list 'quote structure)
:from-end t))
只要我们还提供结构,它就可以按照我们需要的方式工作。例如:
(build-expression '(frog-on bump-on log-on hole-in bottom-of) '(the sea))
;=> (FROG-ON (BUMP-ON (LOG-ON (HOLE-IN (BOTTOM-OF '(THE SEA))))))
(build-expression '(branch-on limb-on tree-in bog-down-in) '(the valley o))
;=> (BRANCH-ON (LIMB-ON (TREE-IN (BOG-DOWN-IN '(THE VALLEY O)))))
现在我们只需要把这些放在一起:
(defun where-is-waldo? (object)
(build-expression
(find-element 'waldo object 'consp '(first rest))
object))
这就像我们想要的那样工作:
(where-is-waldo? '(ralph waldo emerson))
;=> (FIRST (REST '(RALPH WALDO EMERSON)))
(where-is-waldo? '(mentor (ralph waldo emerson) (henry david thoreau)))
;=> (FIRST (REST (FIRST (REST '(MENTOR (RALPH WALDO EMERSON) (HENRY DAVID THOREAU))))))