Iteratively Construct Trie from a Tree - clojure

Introduction
The following function iteratively traverses a tree structure made of nested vectors. It tests each leaf against a predicate. The paths to all leaves which pass that truth-test are returned in a Trie structure. The later describes all found paths in a non-redundant way.
(defn get-trie-of-matches [is? tree]
(loop [[tree i path fk] [tree 0 [] nil]
accum {}]
(cond
(>= i (count tree)) ;; end of level / go up
(if (nil? fk) accum (recur fk accum))
(vector? (tree i)) ;; level down
(recur [(tree i) 0 (conj path i) [tree (inc i) path fk]] accum)
(is? (tree i)) ;; match
(let [new-accum (assoc-in accum (conj path i) {})]
(recur [tree (inc i) path fk] new-accum))
:else ;; next on same level
(recur [tree (inc i) path fk] accum))))
For further explanations see this post.
Example
Consider the following tree
(def tree [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
Applied to the function, using even? as a predicate:
(get-trie-of-matches even? tree)
=> {2 {3 {0 {}, 1 {}}}, 4 {0 {}}}
The result describes the three paths to even numbers in tree. Namely 2-3-0, 2-3-1 and 4-0.
Problem
Even though the above function works, there might be better ways to construct the Trie while traversing the tree. At the moment a hash-map is flooded. On each match via assoc-in. The algorithm traverses the tree structure relatively from level to level but attaches each path in a global fashion to accum, which is not necessary. Also this method is only possible since a hashmap is used. It might anyways be better to use a sequential data-structure for the Trie in order to facilitate further iterations over it. This could not be adopted to the above method.
Question
How could a Trie be created from within the above function get-trie-of-matches without relying on hash-map specific 'global' 'write' functions?

I would propose to take a look at clojure's walk api.
It allows you to recursively apply some function to nested collections.
In this case you could use postwalk:
user> (require '[clojure.walk :as w])
user> (w/postwalk-demo [1 3 [4 [6] 7] [[8]]])
Walked: 1
Walked: 3
Walked: 4
Walked: 6
Walked: [6]
Walked: 7
Walked: [4 [6] 7]
Walked: 8
Walked: [8]
Walked: [[8]]
Walked: [1 3 [4 [6] 7] [[8]]]
[1 3 [4 [6] 7] [[8]]]
The key thing is you can replace any item at every step:
user> (w/postwalk #(if (coll? %) (reverse %) (inc %))
[1 3 [4 [6] 7] [[8]]])
(((9)) (8 (7) 5) 4 2)
Here we increment all the numbers, and reverse all the collections, keeping the nested structure.
Now applying to your task:
You could walk through your tree, keeping just even number's indices and not empty collections (e.g. collections containing even numbers, and not empty collections):
;; helper function
(defn empty-coll? [item]
(and (coll? item) (not (seq item))))
(defn make-trie [pred tree]
(w/postwalk
#(if (coll? %)
(keep-indexed (fn [idx item]
(cond (empty-coll? item) nil
(coll? item) (list idx item)
item idx
:else nil))
%)
(pred %))
tree))
in repl:
user> (def tree [7 9 [7 5 3 [4 6 9] 9 3] 1 [2 7 9 9]])
#'user/tree
user> (make-trie even? tree)
((2 ((3 (0 1)))) (4 (0)))
user> (make-trie #(> % 7) tree)
(1 (2 ((3 (2)) 4)) (4 (2 3)))
The structure is similar to your map. In fact you can produce any structure you want with minor changes to the function, for example your map structure:
(defn make-trie-map [pred tree]
(w/postwalk
#(if (coll? %)
(into {}
(keep-indexed (fn [idx item]
(cond (empty-coll? item) nil
(coll? item) {idx item}
item {idx {}}
:else nil))
%))
(pred %))
tree))
user> (make-trie-map even? tree)
{2 {3 {0 {}, 1 {}}}, 4 {0 {}}}

Related

Clojure: List Substitution Function

I am trying to write a function which, given a collection and a sequence of replacements, replaces any lists in the collection with the next replacement in the order they appear.
For example:
(substitute '(+ 1 (* 2 3) 4 (* 5 6) [:a :b]) => (+ 1 :a 4 :b)
(substitute '[1 (2 3 4) (5 6 7)] [:x :y :z]) => [1 :x :y]
(substitute '[(1 2 3) (4 5 6) (7 8 9)] [:x :y]) => [:x :y (7 8 9)]
Currently I have this:
(defn substitute
[form syms]
(if (seq form)
(lazy-seq
(if (and (not-empty syms) (list? (first form)))
(cons
(first syms)
(substitute (rest form) (rest syms)))
(cons
(first form)
(substitute (rest form) syms))))))
However I have two problems. First, I want the output to be the same type as form. I tried doing (into (empty form) (substitute form syms)) but this causes the output to be reversed when form is a list. Second, I am struggling to find a way to make this work on maps (I want to check for a list in both the key and value of each entry).
Any tips or pointers would be much appreciated. Thanks.
Here's an approach using clojure.walk/prewalk to traverse the form in order (pre-order) and using an atom to track what syms remain for substitution:
(defn substitute [form syms]
(let [syms' (atom syms)
depth (atom 0)]
(walk/prewalk
(fn [v]
(cond
(= 1 (swap! depth inc)) v ;; don't examine the input form itself
(list? v) (if-let [sym (first #syms')]
(do (swap! syms' rest)
sym)
v)
:else v))
form)))
The depth atom is to ensure we don't act on the first value which will be form itself, and if it were a list we wouldn't want to substitute the whole thing. At first I just checked (not= form v) but thought that could backfire if your form contains nested forms identical/equal to the outer form. I suspect there's a better way to accomplish this!
prewalk (and postwalk) also relieve you of having to worry about the type of collection you're walking i.e. lists will come out in the correct order.
(substitute '(+ 1 (* 2 3) 4 (* 5 6)) [:a :b])
=> (+ 1 :a 4 :b)
(substitute '[1 (2 3 4) (5 6 7)] [:x :y :z])
=> [1 :x :y]
(substitute '[(1 2 3) (4 5 6) (7 8 9)] [:x :y])
=> [:x :y (7 8 9)]
Using prewalk also allows this to work on maps w/o additional effort:
(substitute {:foo '(1 2 3) '(4 5 6) "hey"} [:a :b])
=> {:foo :a, :b "hey"}
You can also use prewalk-demo to illustrate how the form is traversed:
(walk/prewalk-demo {:foo '(1 2 3) '(4 5 6) "hey"})
Walked: {:foo (1 2 3), (4 5 6) "hey"}
Walked: [:foo (1 2 3)]
Walked: :foo
Walked: (1 2 3)
Walked: 1
Walked: 2
Walked: 3
Walked: [(4 5 6) "hey"]
Walked: (4 5 6)
Walked: 4
Walked: 5
Walked: 6
Walked: "hey"
Thanks to everyone who replied. I think I may have cracked it now.
(defn map-to-vec
[map]
(reduce-kv (fn [vec k v] (into vec [k v])) [] map))
(defn substitute-seq
[form syms]
(if (seq form)
(lazy-seq
(if (and (not-empty syms) (list? (first form)))
(cons
(first syms)
(substitute-seq (rest form) (rest syms)))
(cons
(first form)
(substitute-seq (rest form) syms))))))
(defn substitute
[form syms]
(cond
(list? form) (apply list (sub-syms-seq form syms))
(map? form)
(reduce
(fn [m [k v]] (conj m [k v]))
(empty form)
(partition 2 (sub-syms-seq (map-to-vec form) syms)))
(coll? form) (into (empty form) (sub-syms-seq form syms))))
I wrote a function map-to-vec which converts a map into a vector, such that {:a 1 :b 2 :c 3} becomes [:a 1 :b 2 :c 3] and added a helper function which ensures that the type of the output is the same as the input and, in the case of the input being a map, performs the main function (now called substitute-seq) on the map-to-veced map and then converts it back into a map again.
I'm sure there's still a better and more efficient way to do this but I suppose this works.

Clojure - Function that returns all the indices of a vector of vectors

If I have a vector [[[1 2 3] [4 5 6] [7 8 9]] [[10 11] [12 13]] [[14] [15]]]
How can I return the positions of each element in the vector?
For example 1 has index [0 0 0], 2 has index [0 0 1], etc
I want something like
(some-fn [[[1 2 3] [4 5 6] [7 8 9]] [[10 11] [12 13]] [[14] [15]]] 1)
=> [0 0 0]
I know that if I have a vector [1 2 3 4], I can do (.indexOf [1 2 3 4] 1) => 0 but how can I extend this to vectors within vectors.
Thanks
and one more solution with zippers:
(require '[clojure.zip :as z])
(defn find-in-vec [x data]
(loop [curr (z/vector-zip data)]
(cond (z/end? curr) nil
(= x (z/node curr)) (let [path (rseq (conj (z/path curr) x))]
(reverse (map #(.indexOf %2 %1) path (rest path))))
:else (recur (z/next curr)))))
user> (find-in-vec 11 data)
(1 0 1)
user> (find-in-vec 12 data)
(1 1 0)
user> (find-in-vec 18 data)
nil
user> (find-in-vec 8 data)
(0 2 1)
the idea is to make a depth-first search for an item, and then reconstruct a path to it, indexing it.
Maybe something like this.
Unlike Asthor's answer it works for any nesting depth (until it runs out of stack). Their answer will give the indices of all items that match, while mine will return the first one. Which one you want depends on the specific use-case.
(defn indexed [coll]
(map-indexed vector coll))
(defn nested-index-of [coll target]
(letfn [(step [indices coll]
(reduce (fn [_ [i x]]
(if (sequential? x)
(when-let [result (step (conj indices i) x)]
(reduced result))
(when (= x target)
(reduced (conj indices i)))))
nil, (indexed coll)))]
(step [] coll)))
(def x [[[1 2 3] [4 5 6] [7 8 9]] [[10 11] [12 13]] [[14] [15]]])
(nested-index-of x 2) ;=> [0 0 1]
(nested-index-of x 15) ;=> [2 1 0]
Edit: Target never changes, so the inner step fn doesn't need it as an argument.
Edit 2: Cause I'm procrastinating here, and recursion is a nice puzzle, maybe you wanted the indices of all matches.
You can tweak my first function slightly to carry around an accumulator.
(defn nested-indices-of [coll target]
(letfn [(step [indices acc coll]
(reduce (fn [acc [i x]]
(if (sequential? x)
(step (conj indices i) acc x)
(if (= x target)
(conj acc (conj indices i))
acc)))
acc, (indexed coll)))]
(step [] [] coll)))
(def y [[[1 2 3] [4 5 6] [7 8 9]] [[10 11] [12 13]] [[14] [15 [16 17 4]]]])
(nested-indices-of y 4) ;=> [[0 1 0] [2 1 1 2]]
Vectors within vectors are no different to ints within vectors:
(.indexOf [[[1 2 3] [4 5 6] [7 8 9]] [[10 11] [12 13]] [[14] [15]]] [[14] [15]])
;;=> 2
The above might be a bit difficult to read, but [[14] [15]] is the third element.
Something like
(defn indexer [vec number]
(for [[x set1] (map-indexed vector vec)
[y set2] (map-indexed vector set1)
[z val] (map-indexed vector set2)
:when (= number val)]
[x y z]))
Written directly into here so not tested. Giving more context on what this would be used for might make it easier to give a good answer as this feels like something you shouldn't end up doing in Clojure.
You can also try and flatten the vectors in some way
An other solution to find the path of every occurrences of a given number.
Usually with functional programming you can go for broader, general, elegant, bite size solution. You will always be able to optimize using language constructs or techniques as you need (tail recursion, use of accumulator, use of lazy-seq, etc)
(defn indexes-of-value [v coll]
(into []
(comp (map-indexed #(if (== v %2) %1))
(remove nil?))
coll))
(defn coord' [v path coll]
(cond
;; node is a leaf: empty or coll of numbers
(or (empty? coll)
(number? (first coll)))
(when-let [indexes (seq (indexes-of-value v coll))]
(map #(conj path %) indexes))
;; node is branch: a coll of colls
(coll? (first coll))
(seq (sequence (comp (map-indexed vector)
(mapcat #(coord' v (conj path (first %)) (second %))))
coll))))
(defn coords [v coll] (coord' v [] coll))
Execution examples:
(def coll [[2 1] [] [7 8 9] [[] [1 2 2 3 2]]])
(coords 2 coll)
=> ([0 0] [3 1 1] [3 1 2] [3 1 4])
As a bonus you can write a function to test if paths are all valid:
(defn valid-coords? [v coll coords]
(->> coords
(map #(get-in coll %))
(remove #(== v %))
empty?))
and try the solution with input generated with clojure.spec:
(s/def ::leaf-vec (s/coll-of nat-int? :kind vector?))
(s/def ::branch-vec (s/or :branch (s/coll-of ::branch-vec :kind vector?
:min-count 1)
:leaf ::leaf-vec))
(let [v 1
coll (first (gen/sample (s/gen ::branch-vec) 1))
res (coords v coll)]
(println "generated coll: " coll)
(if-not (valid-coords? v coll res)
(println "Error:" res)
:ok))
Here is a function that can recursively search for a target value, keeping track of the indexes as it goes:
(ns tst.clj.core
(:use clj.core tupelo.test)
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(defn index-impl
[idxs data tgt]
(apply glue
(for [[idx val] (zip (range (count data)) data)]
(let [idxs-curr (append idxs idx)]
(if (sequential? val)
(index-impl idxs-curr val tgt)
(if (= val tgt)
[{:idxs idxs-curr :val val}]
[nil]))))))
(defn index [data tgt]
(keep-if not-nil? (index-impl [] data tgt)))
(dotest
(let [data-1 [1 2 3]
data-2 [[1 2 3]
[10 11]
[]]
data-3 [[[1 2 3]
[4 5 6]
[7 8 9]]
[[10 11]
[12 13]]
[[20]
[21]]
[[30]]
[[]]]
]
(spyx (index data-1 2))
(spyx (index data-2 10))
(spyx (index data-3 13))
(spyx (index data-3 21))
(spyx (index data-3 99))
))
with results:
(index data-1 2) => [{:idxs [1], :val 2}]
(index data-2 10) => [{:idxs [1 0], :val 10}]
(index data-3 13) => [{:idxs [1 1 1], :val 13}]
(index data-3 21) => [{:idxs [2 1 0], :val 21}]
(index data-3 99) => []
If we add repeated values we get the following:
data-4 [[[1 2 3]
[4 5 6]
[7 8 9]]
[[10 11]
[12 2]]
[[20]
[21]]
[[30]]
[[2]]]
(index data-4 2) => [{:idxs [0 0 1], :val 2}
{:idxs [1 1 1], :val 2}
{:idxs [4 0 0], :val 2}]

clojure - contains?, conj and recur

I'm trying to write a function with recur that cut the sequence as soon as it encounters a repetition ([1 2 3 1 4] should return [1 2 3]), this is my function:
(defn cut-at-repetition [a-seq]
(loop[[head & tail] a-seq, coll '()]
(if (empty? head)
coll
(if (contains? coll head)
coll
(recur (rest tail) (conj coll head))))))
The first problem is with the contains? that throws an exception, I tried replacing it with some but with no success. The second problem is in the recur part which will also throw an exception
You've made several mistakes:
You've used contains? on a sequence. It only works on associative
collections. Use some instead.
You've tested the first element of the sequence (head) for empty?.
Test the whole sequence.
Use a vector to accumulate the answer. conj adds elements to the
front of a list, reversing the answer.
Correcting these, we get
(defn cut-at-repetition [a-seq]
(loop [[head & tail :as all] a-seq, coll []]
(if (empty? all)
coll
(if (some #(= head %) coll)
coll
(recur tail (conj coll head))))))
(cut-at-repetition [1 2 3 1 4])
=> [1 2 3]
The above works, but it's slow, since it scans the whole sequence for every absent element. So better use a set.
Let's call the function take-distinct, since it is similar to take-while. If we follow that precedent and make it lazy, we can do it thus:
(defn take-distinct [coll]
(letfn [(td [seen unseen]
(lazy-seq
(when-let [[x & xs] (seq unseen)]
(when-not (contains? seen x)
(cons x (td (conj seen x) xs))))))]
(td #{} coll)))
We get the expected results for finite sequences:
(map (juxt identity take-distinct) [[] (range 5) [2 3 2]]
=> ([[] nil] [(0 1 2 3 4) (0 1 2 3 4)] [[2 3 2] (2 3)])
And we can take as much as we need from an endless result:
(take 10 (take-distinct (range)))
=> (0 1 2 3 4 5 6 7 8 9)
I would call your eager version take-distinctv, on the map -> mapv precedent. And I'd do it this way:
(defn take-distinctv [coll]
(loop [seen-vec [], seen-set #{}, unseen coll]
(if-let [[x & xs] (seq unseen)]
(if (contains? seen-set x)
seen-vec
(recur (conj seen-vec x) (conj seen-set x) xs))
seen-vec)))
Notice that we carry the seen elements twice:
as a vector, to return as the solution; and
as a set, to test for membership of.
Two of the three mistakes were commented on by #cfrick.
There is a tradeoff between saving a line or two and making the logic as simple & explicit as possible. To make it as obvious as possible, I would do it something like this:
(defn cut-at-repetition
[values]
(loop [remaining-values values
result []]
(if (empty? remaining-values)
result
(let [found-values (into #{} result)
new-value (first remaining-values)]
(if (contains? found-values new-value)
result
(recur
(rest remaining-values)
(conj result new-value)))))))
(cut-at-repetition [1 2 3 1 4]) => [1 2 3]
Also, be sure to bookmark The Clojure Cheatsheet and always keep a browser tab open to it.
I'd like to hear feedback on this utility function which I wrote for myself (uses filter with stateful pred instead of a loop):
(defn my-distinct
"Returns distinct values from a seq, as defined by id-getter."
[id-getter coll]
(let [seen-ids (volatile! #{})
seen? (fn [id] (if-not (contains? #seen-ids id)
(vswap! seen-ids conj id)))]
(filter (comp seen? id-getter) coll)))
(my-distinct identity "abracadabra")
; (\a \b \r \c \d)
(->> (for [i (range 50)] {:id (mod (* i i) 21) :value i})
(my-distinct :id)
pprint)
; ({:id 0, :value 0}
; {:id 1, :value 1}
; {:id 4, :value 2}
; {:id 9, :value 3}
; {:id 16, :value 4}
; {:id 15, :value 6}
; {:id 7, :value 7}
; {:id 18, :value 9})
Docs of filter says "pred must be free of side-effects" but I'm not sure if it is ok in this case. Is filter guaranteed to iterate over the sequence in order and not for example take skips forward?

Easy way to change specific list item in list

In Clojure I want change specific item(list) in list with other.
Here is my structure:
(def myLis '((3 3) (5 5) (5 5)))
(def som '(4 4))
I want change second element in myLis with som.
Result in myLis is '((3 3) (4 4) (5 5))
This is basic example. I have few hundred items in myList.
I try assoc and update-in but this not work on list.
When I try with assoc and update:
(update-in myLis [1] som)
(assoc myLis 1 som)
(assoc-in myLis [1] som)
got error like that:
clojure.lang.PersistentList cannot be cast to clojure.lang.Associative
How can quick change nth element in this structure (list of lists).
As pointed out in the clojure bible (Clojure Programming):
Because [lists] are linked lists, they do not support efficient random
access; thus, nth on a list will run in linear time (as opposed to
constant time when used with vectors, arrays, and so on), and get does
not support lists at all because doing so would not align with get’s
objective of sublinear efficiency.
So in order to replace an element of a list you will have to traverse all the elements up to it, thus running longer the further your item is in the list, and rebuild the list with the elements before it, the new item and all the elements after it (rest). Alternatively, turn the list into a vector, use update-in and back into a list if you absolutely have to use lists.
However, if you can, it would be worth seeing if you can use sequences in your code rather than lists, and thus you can interchangeably use vectors or other abstractions that are more efficient for the processing you are performing over them.
A trivial function that would meet the basics of your requirement with lists however, is:
(defn list-update-in [l i x]
(let [newlist (take i l)
newlist (concat newlist (list x))
newlist (concat newlist (drop (+ 1 i) l))]
newlist))
user> (list-update-in '((1 2) (2 3) (3 4)) 1 '(8 9))
((1 2) (8 9) (3 4))
There's no out of bounds checks on this
Adding an additional answer using loop/recur to realign the OP's own solution to be more lisp like.
(defn list-update-in-recur [l i x]
(loop [new-data [] old-list l]
(if (seq old-list)
(if (= (count new-data) i)
(recur (conj new-data x) (rest old-list))
(recur (conj new-data (first old-list)) (rest old-list)))
(apply list new-data))))
user> (list-update-in-recur '((1 2) (2 3) (3 4)) 1 '(8 9))
((1 2) (8 9) (3 4))
A few points to note:
It's written as a function, there are no 'def' values to set any global value. The final result is the return of the function (apply list new-data)
Arguments initialise the loop, the size of the growing list is used to determine if we want to replace the nth item or not (no index variables)
The passed in list becomes the initial old-list value which reduces in size each iteration, and the exit condition is simply if there are any more elements left in it using the test (seq old-list), which returns false/nil when it is empty.
Because we conj everything (which adds to start of the list) we reverse it to return the output. It now uses a vector to create the new sequence, and converts to a list as the last step instead of reversing a list
I've replaced nth with first and rest which are more efficient and don't have to traverse entire lists every iteration.
This is still very inefficient and only provided as a learning exercise.
You should normally use vectors like [1 2 3] in preference to lists like '(1 2 3) for most purposes. In Clojure, a list is normally used for a function call like (+ 1 2), while for data literals vectors normally used like [1 2 3].
Here is code showing 2 options that work.
Main code:
(ns clj.core
(:require
[tupelo.core :as t]
))
(t/refer-tupelo)
(def myLis [ [3 3] [5 5] [5 5] ] )
(def som [4 4] )
(spyx (assoc myLis 1 som))
(spyx (assoc-in myLis [1] som))
(defn -main [& args]
(println "-main"))
Result:
~/clj > lein run
(assoc myLis 1 som) => [[3 3] [4 4] [5 5]]
(assoc-in myLis [1] som) => [[3 3] [4 4] [5 5]]
You need this in project.clj to make the (spy ...) work:
:dependencies [
[tupelo "0.9.9"]
...
Update 2016-11-2:
If you really want to keep everything in a list, you can use replace-at from the Tupelo library. It works like this:
(def myLis '( (3 3) (5 5) (5 5) ) )
(def vec-1 [4 4] )
(def list-1 '(4 4) )
(spyx (t/replace-at myLis 1 vec-1 ))
(spyx (t/replace-at myLis 1 list-1))
(spyx (apply list (t/replace-at myLis 1 list-1)))
with result
> lein run
(t/replace-at myLis 1 vec-1) => [(3 3) [4 4] (5 5)]
(t/replace-at myLis 1 list-1) => [(3 3) (4 4) (5 5)]
(apply list (t/replace-at myLis 1 list-1)) => ((3 3) (4 4) (5 5))
The first 2 examples show that the new element can be anything, such as the vector [4 4] or the list (4 4). Also, notice that replace-at always returns a vector result. If you want the final result to be a list as well, you need to use (apply list <some-collection>).
My solution with lists:
(def newL '())
(def i 1)
(loop [k (- (count myLis) 1)]
(when (> k -1)
(cond
(= k i) (def newL (conj newL som))
:else (def newL (conj newL (nth myLis k)))
)
(recur (- k 1))
)
)

Find Path to the First Occurrence in a Nested Data Structure

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.
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?))
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"
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.