Given the following tree (or any other form in Clojure including maps and vectors):
'( (a b) (c d) )
I would like to generate a map in Clojure that indexes each sub-form according to a depth-first traversal of the entire form and also provides a vector (or list) of the indices of the form's children (if any).
0 -> a []
1 -> b []
2 -> (a b) [0 1]
3 -> c []
4 -> d []
5 -> (c d) [3 4]
6 -> ( (a b) (c d) ) [2 5]
I have so far only managed to use clojure.walk to produce the first part (indexing the subforms) but I am baffled as to how to generate the indices of the children as well. My code is appended at the end and produces:
user=> (depthFirstIndexing '( (a b) (c d) ))
{6 ((a b) (c d)), 5 (c d), 4 d, 3 c, 2 (a b), 1 b, 0 a}
So the indexes to the sub-forms are generated correctly according to depth-first traversal but I don't see how I can obtain the indices of the children of every sub-form. I tried to use the zippers module but I couldn't see how to perform a depth-first traversal to collect the indices.
half-way there code
(use 'clojure.walk)
(defn depthFirstIndexing [aform]
(let [counter (atom -1)
idxToSubform (atom {})
]
(postwalk (fn [x]
(def idx (swap! counter inc))
(swap! idxToSubform assoc idx x)
x)
aform)
#idxToSubform))
A walk is recursive and does not provide for an accumulator argument, which is why you have had to resort to updating atoms.
A zipper is iterative, so you can carry along other information without breaking a functional pattern.
The natural depth-first traversal is a pre-order traversal, but you are after a post-order, so this requires a little extra work.
Here is a post-order traversal using zippers:
(require '[clojure.zip :as z])
(defn dfs-post-order-traversal [zipper]
(loop [loc zipper, a []]
(cond
(z/end? loc)
(conj a (z/node loc))
(z/branch? loc)
(recur (z/next loc) a)
:else
(recur
(z/next loc)
(into
(conj a (z/node loc))
(reverse
(drop
((fnil count [nil]) (z/path (z/next loc)))
(z/path loc))))))))
And the test case:
(dfs-post-order-traversal (z/seq-zip '((a b) (c d))))
=> [a b (a b) c d (c d) ((a b) (c d))]
Now to finish off your request, we need to map tree locations back to their indices:
(defn dfs-post-order-indexing [branch? children root]
(let [pot (dfs-post-order-traversal (z/zipper branch? children conj root))
m (zipmap pot (range))]
(for [n pot] [(m n) n (if (branch? n) (map m (children n)) (list))])))
(dfs-post-order-indexing seq? identity '((a b) (c d)))
=> ([0 a ()]
[1 b ()]
[2 (a b) (0 1)]
[3 c ()]
[4 d ()]
[5 (c d) (3 4)]
[6 ((a b) (c d)) (2 5)])
Something more exotic:
(dfs-post-order-indexing coll? seq [{:a :b :c :d} :e [:f [:g '(:h :i)]]])
=> ([0 :a ()]
[1 :b ()]
[2 [:a :b] (0 1)]
[3 :c ()]
[4 :d ()]
[5 [:c :d] (3 4)]
[6 {:a :b, :c :d} (2 5)]
[7 :e ()]
[8 :f ()]
[9 :g ()]
[10 :h ()]
[11 :i ()]
[12 (:h :i) (10 11)]
[13 [:g (:h :i)] (9 12)]
[14 [:f [:g (:h :i)]] (8 13)]
[15 [{:a :b, :c :d} :e [:f [:g (:h :i)]]] (6 7 14)])
(use '[clojure.walk :only (postwalk)])
(use '[clojure.set :only (map-invert)])
(defn idx [col]
(let [m (map vector
(range)
(let [v (atom [])]
(postwalk (fn [f] (swap! v conj f) f) col)
#v))
rm (map-invert m)]
(into {} (map (fn [[i e]]
[i [e (if (sequential? e)
(mapv rm e)
[])]])
m))))
(idx '((a b) (c d)))
=> {0 [a []],
1 [b []],
2 [(a b) [0 1]],
3 [c []],
4 [d []],
5 [(c d) [3 4]],
6 [((a b) (c d)) [2 5]]}
Related
When translate a function flatten from Scheme to Clojure, I have encountered
an error "Execution error (IllegalArgumentException)", Please feel free to comment.
Clojure (Encounter error)
(defn flatten [x]
(cond
(empty? x) '()
(not (coll? x)) (list x)
:else (conj (flatten (first x)) (flatten (rest x)))))
(flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ()))
Scheme
> (define (flatten x)
(cond ((null? x) '())
((not (pair? x)) (list x))
(else (append (flatten (car x))
(flatten (cdr x))))))
> (flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ()))
(1 2 3 4 5 6 7 8)
Here is an implementation that works for me
(defn flatten [x]
(cond
(not (coll? x)) (list x)
(empty? x) '()
:else (concat (flatten (first x)) (flatten (rest x)))))
(comment
(flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ())))
;returns (1 2 3 4 5 6 7 8)
The only difference is that I put first the (not (coll? x)) and then the empty? (because (empty? 3) will throw an error)
The other change is the concat instead of conj. I don't think it's recommended to use concat because it can throw a Stack Overflow Error (see : https://stuartsierra.com/2015/04/26/clojure-donts-concat) but it works in this case ...
One more version, based on Alan Thompson's solution:
(defn flat [data]
(reduce (fn [result e]
(if (not (sequential? e))
(conj result e)
(into result (flat e))))
[] data))
Tests:
(clojure.test/are [result arg]
(= result (flat arg))
[1 2 3] [1 2 3]
[1 2 3] [1 [2 3]]
[1 2 3] [1 [2 [3]]]
[0 1 2 3 4 5 6 7] [[0] [1 2 3] [4 5 [6 7]]])
=> true
Here is an alternate version that may be a bit more straightforward:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn flatten
[data]
(loop [result []
items data]
(if (empty? items)
result
(let [item (first items)
items-next (rest items)]
(if (not (sequential? item))
(recur
(conj result item)
items-next)
(recur
(into result (flatten item))
items-next))))))
(dotest
(is= (flatten [1 2 3]) [1 2 3])
(is= (flatten [1 [2 3]]) [1 2 3])
(is= (flatten [1 [2 [3]]]) [1 2 3])
(is= (flatten [[0] [1 2 3] [4 5 [6 7]]]) [0 1 2 3 4 5 6 7])
; Cannot flatten a scalar value - we don't want (flatten 3) => [3]
(throws? (flatten 3)))
Note that we avoid tricks like wrapping every scalar value in a list, so we don't get the non-intuitive result (flatten 3) => [3].
Build using my favorite template project.
How do you swap adjacent elements in an input sequence using clojure.
[10 15 20 25] ---> [15 10 25 20]
[:q :e :g :t :p] ---> [:e :q :t :g :p]
this is how i did it, but pretty sure there are better ways to do it!
(defn switch [s]
(loop [[a b & rest] s
result []]
(if (empty? rest)
(cond
;;empty sequence
(empty? s) result
;;odd sequence
(nil? b) (conj result a)
;;even sequence
:else (conj result b a)
)
(recur rest (conj result b a))))
)
(let [A [:q :e :g :t :p]]
(->> A
(partition-all 2)
(mapcat reverse)))
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}]
hi guys how to collect data from if condition in a loop
(defn update-f [a b]
(let[data(loop [i 0]
(when (< i (count a))
;;(println i)
(if (every? (set b) (nth (for [x a] (set (list x))) i))
(nth (for [x a] (conj x "new")) i)
(nth (for [x a] (conj x "old")) i))
(recur (inc i))))])); loop i will take this value
i can print data from if condition i need take data from this function
You can collect data in a loop by adding an extra "accumulator" parameter:
(loop [i 0 acc []]
(if (< i 5)
(recur (inc i) (if (even? i)
(conj acc i)
acc))
acc))
However, it seems that what you are trying to achieve could be written much more concise (if I got what you want right):
(defn are-new? [a b]
(map (fn[i] [i (if (b i) :new :old)]) a))
We are mapping every item in a to add a :new or :old tag to it. A set also acts as a function, that returns a truthy value when the argument exists in the set. So to find if an item i is already in set b, we can simply do (b i).
And you can test it:
(let [r (are-new? [[1 2 3] [2 3 4] [1 2]] #{[1 2 3] [1 2]})]
(doseq [i r]
(println i)))
> [[1 2 3] :new]
> [[2 3 4] :old]
> [[1 2] :new]
I have a sequence s and a list of indexes into this sequence indexes. How do I retain only the items given via the indexes?
Simple example:
(filter-by-index '(a b c d e f g) '(0 2 3 4)) ; => (a c d e)
My usecase:
(filter-by-index '(c c# d d# e f f# g g# a a# b) '(0 2 4 5 7 9 11)) ; => (c d e f g a b)
You can use keep-indexed:
(defn filter-by-index [coll idxs]
(keep-indexed #(when ((set idxs) %1) %2)
coll))
Another version using explicit recur and lazy-seq:
(defn filter-by-index [coll idxs]
(lazy-seq
(when-let [idx (first idxs)]
(if (zero? idx)
(cons (first coll)
(filter-by-index (rest coll) (rest (map dec idxs))))
(filter-by-index (drop idx coll)
(map #(- % idx) idxs))))))
make a list of vectors containing the items combined with the indexes,
(def with-indexes (map #(vector %1 %2 ) ['a 'b 'c 'd 'e 'f] (range)))
#'clojure.core/with-indexes
with-indexes
([a 0] [b 1] [c 2] [d 3] [e 4] [f 5])
filter this list
lojure.core=> (def filtered (filter #(#{1 3 5 7} (second % )) with-indexes))
#'clojure.core/filtered
clojure.core=> filtered
([b 1] [d 3] [f 5])
then remove the indexes.
clojure.core=> (map first filtered)
(b d f)
then we thread it together with the "thread last" macro
(defn filter-by-index [coll idxs]
(->> coll
(map #(vector %1 %2)(range))
(filter #(idxs (first %)))
(map second)))
clojure.core=> (filter-by-index ['a 'b 'c 'd 'e 'f 'g] #{2 3 1 6})
(b c d g)
The moral of the story is, break it into small independent parts, test them, then compose them into a working function.
The easiest solution is to use map:
(defn filter-by-index [coll idx]
(map (partial nth coll) idx))
I like Jonas's answer, but neither version will work well for an infinite sequence of indices: the first tries to create an infinite set, and the latter runs into a stack overflow by layering too many unrealized lazy sequences on top of each other. To avoid both problems you have to do slightly more manual work:
(defn filter-by-index [coll idxs]
((fn helper [coll idxs offset]
(lazy-seq
(when-let [idx (first idxs)]
(if (= idx offset)
(cons (first coll)
(helper (rest coll) (rest idxs) (inc offset)))
(helper (rest coll) idxs (inc offset))))))
coll idxs 0))
With this version, both coll and idxs can be infinite and you will still have no problems:
user> (nth (filter-by-index (range) (iterate #(+ 2 %) 0)) 1e6)
2000000
Edit: not trying to single out Jonas's answer: none of the other solutions work for infinite index sequences, which is why I felt a solution that does is needed.
I had a similar use case and came up with another easy solution. This one expects vectors.
I've changed the function name to match other similar clojure functions.
(defn select-indices [coll indices]
(reverse (vals (select-keys coll indices))))
(defn filter-by-index [seq idxs]
(let [idxs (into #{} idxs)]
(reduce (fn [h [char idx]]
(if (contains? idxs idx)
(conj h char) h))
[] (partition 2 (interleave seq (iterate inc 0))))))
(filter-by-index [\a \b \c \d \e \f \g] [0 2 3 4])
=>[\a \c \d \e]
=> (defn filter-by-index [src indexes]
(reduce (fn [a i] (conj a (nth src i))) [] indexes))
=> (filter-by-index '(a b c d e f g) '(0 2 3 4))
[a c d e]
I know this is not what was asked, but after reading these answers, I realized in my own personal use case, what I actually wanted was basically filtering by a mask.
So here was my take. Hopefully this will help someone else.
(defn filter-by-mask [coll mask]
(filter some? (map #(if %1 %2) mask coll)))
(defn make-errors-mask [coll]
(map #(nil? (:error %)) coll))
Usage
(let [v [{} {:error 3} {:ok 2} {:error 4 :yea 7}]
data ["one" "two" "three" "four"]
mask (make-errors-mask v)]
(filter-by-mask data mask))
; ==> ("one" "three")