问题
Consider the following numbers nested by vectors in a tree-structure
(def tree [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
My goal is to find the path to the first even number that can be found by traversing the tree: In the upper example this would be 4, the path from the root to this node would be [2 3 0]
(def result [2 3 0])
I got some difficulties writing a function tho archive this. However, the following function finds the first even number, not its path:
(defn find-even [x]
(if (vector? x)
(some #(when-let [subresult (find-even %)]
(when (even? subresult)
subresult)) x)
x))
(find-even tree) ;; 4
What would I have to do in order to receive the path to the result?
EDIT
I figured out a way to do it. Here is a function that - at least - works:
(defn find-even-path [x]
(letfn [(traverse [x]
(if (vector? x)
(some (fn [[i v]] (when-let [subresult (traverse v)]
(when (even? (:value subresult))
(update subresult :path conj i))))
(map-indexed vector x))
{:path '()
:value x}))]
(when-let [result (traverse x)]
(vec (:path result)))))
(find-even-path tree) ;; [2 3 0]
However, I'd still be curious to hear what could be optimized here. It still looks quite complex to me and is not tail recursive yet.
回答1:
Here's a way to do it tail-recursively:
(defn tailrec-depth-first-path [is? tree]
(loop [[tree i path fk] [tree 0 [] nil]]
(cond
(>= i (count tree))
(if (nil? fk) nil (recur fk))
(vector? (tree i))
(recur [(tree i) 0 (conj path i) [tree (inc i) path fk]])
(is? (tree i))
(conj path i)
:else
(recur [tree (inc i) path fk]))))
(tailrec-depth-first-path even? [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
=> [2 3 0]
The function adds one more index to path
each time it descends further down the tree. The main trick of note here is the use of a "failure continuation", represented by the variable fk
. fk
is the next set of arguments to pass to loop
to continue the search after a failed search of a subtree, or nil if the search is at the top level. This enables backtracking without violating tail-recursion. In other words, the information that, in a non-tail-recursive version, would be needed to do the work remaining after the recursive call, is accumulated in fk
in the tail-recursive version.
A quick test on my 2009 MacBook Air (1.86 GHz Core 2 Duo) suggests that the tail-recursive version is the fastest of the answers posted so far:
(def tree [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
(time (dotimes [_ 100000] (find-even-path tree)))
"Elapsed time: 1153.137475 msecs"
(time (dotimes [_ 100000] (first (findt tree even? []))))
"Elapsed time: 1413.502082 msecs"
(time (dotimes [_ 100000] (depth-first-path even? tree)))
"Elapsed time: 690.56115 msecs"
(time (dotimes [_ 100000] (tailrec-depth-first-path even? tree)))
"Elapsed time: 524.332278 msecs"
回答2:
Here is an option. The idea is to keep a "stacktrace" of indices while traversing the list (the r
argument). Every time we find an item that satisfies the p
predicate, we return that "stacktrace". If none was found, we simply return nil. mapcat
concatenates all the non-empty (non-nil) lists into one resulting list:
(defn findt [t p r]
(mapcat (fn[i c]
(if (coll? c)
(findt c p (cons i r))
(when (p c) [(reverse (cons i r))]))) (range) t))
Its still not tail recursive, but it can find all paths (lazily, due to the use of mapcat
):
(def tree [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
(findt tree even? [])
=> ((2 3 0) (2 3 1) (4 0))
And we can test it with:
(->> (findt tree odd? [])
(map #(get-in tree %))
(every? odd?))
回答3:
This isn't tail-recursive, but it's straightforward:
(defn depth-first-path
([is? tree]
(depth-first-path is? tree []))
([is? tree path-so-far]
(cond
(vector? tree)
(some identity (map #(depth-first-path is? %1 (conj path-so-far %2))
tree
(range)))
(is? tree)
path-so-far
:else
nil)))
(depth-first-path even? [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
=> [2 3 0]
I called it depth-first-path
because there are other reasonable ways to define "first" when searching a tree.
Note: I'm new to Clojure, and I haven't even looked at clojure.walk or Specter. There is probably an easier way.
来源:https://stackoverflow.com/questions/36808383/find-path-to-the-first-occurrence-in-a-nested-data-structure