もう少し一般的な問題に取り組み、その問題を目前の特定の問題に特化する方法を見つけた方が簡単な場合もあります。この場合、何らかの構造体と、その構造体のサブ構造体にアクセスできるいくつかのアクセサーが渡されます。検索する要素と検索するものが与えられた場合、その要素が要素であるかどうかを確認し、要素である場合はそれまでのパスを (適切な形式で) 返し、そうでない場合はそれがアクセサで分解できる構造があれば、分解した部分をそれぞれ試してみてください。
(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))))))