Trying to 'group' and flatten values from nested structure in clojure - clojure

Another restless night trying to figure out how to do this. Given the nested data structure which I've reformatted for readability looking for malformed declaration:
(def tm (list
{:a 1 :b 2 :c 3 :child (list
{:a 4 :b 5 :c 6 :child (list
{:a 40 :b 50 :c 60 :child (list {:a nil :b nil :c nil})})}
{:a 70 :b 80 :c 90 :child (list {:a nil :b nil :c nil})})}
{:a 400 :b 500 :c 600 :child (list {:a nil :b nil :c nil})}
{:a 7 :b 8 :c 9 :child (list
{:a 10 :b 11 :c 12 :child (list {:a nil :b nil :c nil})})})
)
And the traversal can be seen in dg123 example below
I'm getting the following:
=>(roll-down tm :a)
((1 4 40) (1 70) (7 10))
But was expecting:
((1 4 40) (1 70) (400) (7 10))

Here's a zippers based solution
(require '[clojure.zip :as z])
(defn root-to-leaf-paths [t]
(loop [t t paths []]
(cond
(z/end? t) paths
(z/branch? t) (recur (z/next t) paths)
:leaf (recur (z/next t) (conj paths (z/path t))))))
(defn roll-down [d kw]
(->> (z/zipper :child :child nil {:child d})
(root-to-leaf-paths)
(map (comp rest (partial map kw)))))
Note: Your tm isn't a tree but a list of trees. So, here we first turn this structure into a tree by placing the list of trees under one parent root {:child d}. Then we drop this from the resulting paths (comp rest ...).
Examples
(roll-down tm :a)
;=> ((1 4 40) (1 70) (400) (7 10))
(roll-down tm :b)
;=> ((2 5 50) (2 80) (500) (8 11))
(roll-down tm :c)
;=> ((3 6 60) (3 90) (600) (9 12))

a short version use mapcat:
(defn roll-down [g k]
(mapcat
#(if-let [c (:child %)]
(map (fn [x] (conj x (k %))) (roll-down c k))
[[(k %)]])
g))
-> (roll-down tm :a)
([nil 40 4 1] [nil 70 1] [nil 400] [nil 10 7])
This solution uses a depth first traverse and also treat nil as a path. If you like you can just do a remove and reverse on the result.

Here's a more functional approach that doesn't use mutable state:
(defn roll-down-helper [c kw]
(when-let [root-val (kw c)]
(cons root-val
(filter (comp not nil?)
(for [node (:child c)]
(roll-down-helper node kw))))))
(defn get-paths [[x & ys]]
(if (empty? ys)
(list (list x))
(for [y ys]
(flatten (cons x y)))))
(defn roll-down [c kw]
(apply concat
(map (comp get-paths #(roll-down-helper % kw)) c)))
Then you can do the following:
user> (roll-down tm :a)
((1 4 40) (1 70) (400) (7 10))
user> (roll-down tm :b)
((2 5 50) (2 80) (500) (8 11))
user> (roll-down tm :c)
((3 6 60) (3 90) (600) (9 12))

You could simply map your extractor onto your data
(defn get-as [tm]
(let [results (atom [])]
(clojure.walk/prewalk
#(do (if-let [uid (:a %)]
(swap! results conj :a (:a %)))
%)
tm)
#results))
(map get-as tm) ; => ([:a 1 :a 4] [:a 7 :a 10])

Related

Clojure nested for loop with index

I've been trying to idiomatically loop through a nested vector like below:
[[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]]
I also need to return the coordinates once I've found a value.
eg The call (find-key-value 3) should return [1 2]
This is what I have so far but its not giving me the output that I need it would return ([] [] [] [] [] [1 2] [] [] []) where as i only need [1 2]
(defn find-key-value
[array value]
(for [x (range 0 (count array))]
(loop [y 0
ret []]
(cond
(= y (count (nth array x))) [x y]
:else (if (= value (get-in array [x y]))
(recur (+ 1 y) (conj ret [x y]))
(recur (+ 1 y) ret))))))
Anyone have any ideas on how I can fix my code to get to my desired solution or have a better approach in mind!
A list comprehension can be used to find coordinates of all values satisfying a predicate:
(defn find-locs [pred coll]
(for [[i vals] (map-indexed vector coll)
[j val] (map-indexed vector vals)
:when (pred val)]
[i j]))
(find-locs #(= 3 %) [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
=> ([1 5])
(find-locs zero? [[0 1 1] [1 1 1] [1 0 1]])
=> ([0 0] [2 1])
The posed question seems to imply that the keywords in the inputs should be ignored, in which case the answer becomes:
(defn find-locs-ignore-keyword [pred coll]
(for [[i vals] (map-indexed vector coll)
[j val] (map-indexed vector (remove keyword? vals))
:when (pred val)]
[i j]))
(find-locs-ignore-keyword #(= 3 %) [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
=> ([1 2])
there is a function in clojure core, which exactly suites the task: keep-indexed. Which is exactly indexed map + filter:
(defn find-val-idx [v data]
(ffirst (keep-indexed
(fn [i row]
(seq (keep-indexed
(fn [j [_ x]] (when (= v x) [i j]))
(partition 2 row))))
data)))
user> (find-val-idx 3 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> [1 2]
user> (find-val-idx 10 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> nil
user> (find-val-idx 1 [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]])
;;=> [0 0]
There is a map-indexed that is sometimes helpful. See the Clojure Cheatsheet and other docs listed here.
==> Could you please edit the question to clarify the search conditions?
Here is an outline of what you could do to search for the desired answer:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn coords
[data pred]
(let [result (atom [])]
(doseq [row (range (count data))
col (range (count (first data)))]
(let [elem (get-in data [row col])
keeper? (pred elem)]
(when keeper?
(swap! result conj [row col]))))
(deref result)))
(dotest
(let [data [[11 12 13]
[21 22 23]
[31 32 33]]
ends-in-2? (fn [x] (zero? (mod x 2)))]
(is= (coords data ends-in-2?)
[[0 1]
[1 1]
[2 1]])))
It is based on the same template project as the docs. There are many variations (for example, you could use reduce instead of an atom).
Please review the docs listed above.
(defn vec-to-map [v] (into {} (into [] (map vec (partition 2 v)))))
(defn vec-vals [v] (vals (vec-to-map v)))
(defn map-vec-index [v el] (.indexOf (vec-vals v) el))
(defn find-val-coord
([arr val] (find-val-coord arr val 0))
([arr val counter]
(let [row (first arr)
idx (map-vec-index row val)]
(cond (< 0 idx) [counter idx]
:else (recur (rest arr) val (inc counter))))))
(find-val-coord arr 3) ;; => [1 2]
We can also write functions to pick value or corresponding key
from array when coordinate is given:
(defn vec-keys [v] (keys (vec-to-map v)))
(defn get-val-coord [arr coord]
(nth (vec-vals (nth arr (first coord))) (second coord)))
(defn get-key-coord [arr coord]
(nth (vec-keys (nth arr (first coord))) (second coord)))
(get-val-coord arr [1 2]) ;; => 3
(get-key-coord arr [1 2]) ;; => :c
I might be over-engineering this answer slightly, but here is a non-recursive and non-lazy approach based on a single loop that will work for arbitrary and mixed levels of nesting and won't suffer from stack overflow due to recursion:
(defn find-key-value [array value]
(loop [remain [[[] array]]]
(if (empty? remain)
nil
(let [[[path x] & remain] remain]
(cond (= x value) path
(sequential? x)
(recur (into remain
(comp (remove keyword?)
(map-indexed (fn [i x] [(conj path i) x])))
x))
:default (recur remain))))))
(find-key-value [[:a 1 :b 1 :c 1] [:a 1 :b 1 :c 3] [:a 1 :b 1 :c 1]] 3)
;; => [1 2]
(find-key-value [[:a 1 [[[[[:c]]]] [[[9 [[[3]] :k]] 119]]]] [:a [[[1]]] :b 1]] 3)
;; => [0 1 1 0 0 1 0 0 0]
(find-key-value (last (take 20000 (iterate vector 3))) 3)
;; => [0 0 0 0 0 0 0 0 0 0 0 0 0 ...]
A simpler solution, assuming 2D array where the inner vectors are
key value vectors, uses flattening of the 2D array and .indexOf.
(defn find-coord [arr val]
(let [m (count (first arr))
idx (.indexOf (flatten arr) val)]
[(quot idx m) (quot (dec (mod idx m)) 2)]))
(find-coord arr 3) ;;=> [1 2]

Unquoting without namespace

I need to quote without namespace and combine it with unquoting. Something like:
'[a b ~c]
Unfortunately, unquoting works only with syntactic quoting:
`[a b ~c]
But then it expands to
[user/a user/b 7]
I would like to expand without namespaces.
What was suggested on clojurians slack channel is as follows:
Use a combination of "quote unquote" for symbols to get rid of namespaces:
`[~'a ~'b ~c]
and this works perfectly.
As a reference, I have been working on a similar capability that doesn't require defensive treatment like ~'a for each symbol you wish to remain unchanged. It isn't published yet, but here is the technique:
;-----------------------------------------------------------------------------
(defn unquote-form?
[arg]
(and (list? arg)
(= (quote unquote) (first arg))))
(defn unquote-splicing-form?
[arg]
(and (list? arg)
(= (quote unquote-splicing) (first arg))))
(defn quote-template-impl
[form]
(walk/prewalk
(fn [item]
(cond
(unquote-form? item) (eval (xsecond item))
(sequential? item) (let [unquoted-vec (apply glue
(forv [it item]
(if (unquote-splicing-form? it)
(eval (xsecond it))
[it])))
final-result (if (list? item)
(t/->list unquoted-vec)
unquoted-vec)]
final-result)
:else item))
form))
(defmacro quote-template
[form]
(quote-template-impl form))
and unit tests to show it in action:
;-----------------------------------------------------------------------------
(def vec234 [2 3 4])
(dotest
(is (td/unquote-form? (quote (unquote (+ 2 3)))))
(is (td/unquote-splicing-form? (quote (unquote-splicing (+ 2 3)))))
(is= (td/quote-template {:a 1 :b (unquote (+ 2 3))})
{:a 1, :b 5})
(is= (td/quote-template {:a 1 :b (unquote (vec (range 3)))})
{:a 1, :b [0 1 2]})
(is= (td/quote-template {:a 1 :b (unquote vec234)})
{:a 1, :b [2 3 4]})
(let [result (td/quote-template (list 1 2 (unquote (inc 2)) 4 5))]
(is (list? result))
(is= result (quote (1 2 3 4 5))))
(is= (td/quote-template [1 (unquote-splicing vec234) 5]) ; unqualified name OK here
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote-splicing (t/thru 2 4)) 5])
[1 2 3 4 5])
(is= (td/quote-template [1 (unquote (t/thru 2 4)) 5])
[1 [2 3 4] 5])
)
So, instead of Clojure's syntax-quote (i.e. backquote), you can use quote-template. Then, use either unquote or unquote-splicing to insert values into the quoted template without having the namespace prepended to other symbols.
I know it is probably not the answer you are looking for by why not use code to construct that thing you want? Why force it to be all done in one expression? You could for example use
(conj '[a b] c)

Clojure - Combine two lists by index

How would I combine two lists say '(1 2 3 4) and '(:a :b :c :d) to get
(1 :a 2 :b 3 :c 4 :d)
As I can't just do concat as that would add the second list to the end of the first list.
I thought about doing something like
user=> (def a '(1 2 3 4))
user=> (def b '(:a :b :c :d))
user=> (def x (apply conj (second (split-at 1 a)) (nth b 0) (reverse (first (split-at 1 a)))))
(1 :a 2 3 4)
user=> (def y (apply conj (second (split-at 3 x)) (nth b 1) (reverse (first (split-at 3 x)))))
(1 :a 2 :b 3 4)
user=> (def z (apply conj (second (split-at 5 y)) (nth b 2) (reverse (first (split-at 5 y)))))
(1 :a 2 :b 3 :c 4)
user=> (def q (apply conj (second (split-at 7 z)) (nth b 3) (reverse (first (split-at 7 z)))))
(1 :a 2 :b 3 :c 4 :d)
But I think there is a better way
Any help would be much appreciated
Use interleave:
(interleave '(1 2 3 4) '(:a :b :c :d))
=> (1 :a 2 :b 3 :c 4 :d)
An alternative to interleave:
(mapcat list '(1 2 3 4) '(:a :b :c :d))
;;=> (1 :a 2 :b 3 :c 4 :d)
Here mapcat is effectively doing the cat operation after the double map operation has correctly ordered the elements, albeit in list tuples, so '(1 :a) etc.
More explicitly:
(apply concat (map list '(1 2 3 4) '(:a :b :c :d))))
;;=> (1 :a 2 :b 3 :c 4 :d)
Thus if your first list really is increasing integer values then you don't have to generate them:
(apply concat (map-indexed list '(:a :b :c :d)))
;;=> (0 :a 1 :b 2 :c 3 :d)

Clojure: how to move vector elements in a map elegantly

In clojure, I am trying to accomplish the following logic:
Input:
{:a [11 22 33] :b [10 20 30]}, 2
Output:
{:a [11] :b [10 20 30 22 33]}
i.e. Move the last 2 elements from :a to :b
Is there a clojurish way for this operation?
Since you're effectively modifying both mappings in the map, it's probably easiest to explicitly deconstruct the map and just return the new map via a literal, using subvec and into for the vector manipulation:
(defn move [m n]
(let [{:keys [a b]} m
i (- (count a) n)
left (subvec a 0 i)
right (subvec a i)]
{:a left :b (into b right)}))
(move {:a [11 22 33] :b [10 20 30]} 2)
;;=> {:a [11], :b [10 20 30 22 33]}
As a bonus, this particular implementation is both very idiomatic and very fast.
Alternatively, using the split-at' function from here, you could write it like this:
(defn split-at' [n v]
[(subvec v 0 n) (subvec v n)])
(defn move [m n]
(let [{:keys [a b]} m
[left right] (split-at' (- (count a) n) a)]
{:a left :b (into b right)}))
First, using the sub-vec in the other answers will throw an IndexOutOfBoundsException when the number of elements to be moved is greater than the size of the collection.
Secondly, the destructuring, the way most have done here, couples the function to one specific data structure. This being, a map with keys :a and :b and values for these keys that are vectors. Now if you change one of the keys in the input, then you need to also change it in move function.
My solution follows:
(defn move [colla collb n]
(let [newb (into (into [] collb) (take-last n colla))
newa (into [] (drop-last n colla))]
[newa newb]))
This should work for any collection and will return vector of 2 vectors. My solution is far more reusable. Try:
(move (range 100000) (range 200000) 10000)
Edit:
Now you can use first and second to access the vector you need in the return.
I would do it just a little differently than Josh:
(defn tx-vals [ {:keys [a b]} num-to-move ]
{:a (drop-last num-to-move a)
:b (concat b (take-last num-to-move a)) } )
(tx-vals {:a [11 22 33], :b [10 20 30]} 2)
=> {:a (11), :b (10 20 30 22 33)}
Update
Sometimes it may be more convenient to use the clojure.core/split-at function as follows:
(defn tx-vals-2 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{ :a a-head
:b (concat b a-tail) } ))
If vectors are preferred on output (my favorite!), just do:
(defn tx-vals-3 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{:a (vec a-head)
:b (vec (concat b a-tail))} ))
to get the results:
(tx-vals-2 data 2) => {:a (11), :b (10 20 30 22 33)}
(tx-vals-3 data 2) => {:a [11], :b [10 20 30 22 33]}
(defn f [{:keys [a b]} n]
(let [last-n (take-last n a)]
{:a (into [] (take (- (count a) n) a))
:b (into b last-n)}))
(f {:a [11 22 33] :b [10 20 30]} 2)
=> {:a [11], :b [10 20 30 22 33]}
In case if the order of those items does not matter, here is my attempt:
(def m {:a [11 22 33] :b [10 20 30]})
(defn so-42476918 [{:keys [a b]} n]
(zipmap [:a :b] (map vec (split-at (- (count a) n) (concat a b)))))
(so-42476918 m 2)
gives:
{:a [11], :b [22 33 10 20 30]}
i would go with an approach, which differs a bit from the previous answers (well, technically it is the same, but it differs on the application-scale level).
First of all, transferring data between two collections is quite a frequent task, so it at least deserves some special utility function for that in your library:
(defn transfer [from to n & {:keys [get-from put-to]
:or {:get-from :start :put-to :end}}]
(let [f (if (= get-from :end)
(partial split-at (- (count from) n))
(comp reverse (partial split-at n)))
[from swap] (f from)]
[from (if (= put-to :start)
(concat swap to)
(concat to swap))]))
ok, it looks verbose, but it lets you transfer data from start/end of one collection to start/end of the other:
user> (transfer [1 2 3] [4 5 6] 2)
[(3) (4 5 6 1 2)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end)
[(1) (4 5 6 2 3)]
user> (transfer [1 2 3] [4 5 6] 2 :put-to :start)
[(3) (1 2 4 5 6)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end :put-to :start)
[(1) (2 3 4 5 6)]
So what's left, is to make your domain specific function on top of it:
(defn move [data n]
(let [[from to] (transfer (:a data) (:b data) n
:get-from :end
:put-to :end)]
(assoc data
:a (vec from)
:b (vec to))))
user> (move {:a [1 2 3 4 5] :b [10 20 30 40] :c [:x :y]} 3)
{:a [1 2], :b [10 20 30 40 3 4 5], :c [:x :y]}

make sequence side-effectfull in Clojure

What I want to do is like following.
(def mystream (stream (range 100)))
(take 3 mystream)
;=> (0 1 2)
(take 3 mystream)
;=> (3 4 5)
(first (drop 1 mystream))
;=> 7
The stream function make sequence side-effectfull like io stream.
I think this is almost impossible.
Here is my attempt.
(defprotocol Stream (first! [this]))
(defn stream [lst]
(let [alst (atom lst)]
(reify Stream
(first! [this]
(let [[fs] #alst]
(swap! alst rest)
fs)))))
(let [mystream (stream (iterate inc 1))]
(map #(if (string? %) (first! mystream) %)
[:a "e" "b" :c "i" :f]))
;=> (:a 1 2 :c 3 :f)
Unfotunately this approach need to implement all function I will use.
Judging by your followup comment to Maurits, you don't need mutation, but rather simply need to emit a new sequence with the elements in the right place.
For example:
(defn replace-when [pred coll replacements]
(lazy-seq
(when (seq coll)
(if (seq replacements)
(if (pred (first coll))
(cons (first replacements)
(replace-when pred (rest coll) (rest replacements)))
(cons (first coll)
(replace-when pred (rest coll) replacements)))
coll))))
user=> (def seq1 [:a :b :c])
#'user/seq1
user=> (def seq2 [:x "i" "u" :y :z "e"])
#'user/seq2
user=> (replace-when string? seq2 seq1)
(:x :a :b :y :z :c)
This won't work with the standard take and drop, but you could quite easily write your own to work on a mutable atom, e.g. you could do something like this:
(def mystream (atom (range 100)))
(defn my-take [n stream]
(let [data #stream
result (take n data)]
(reset! stream (drop n data))
result))
(my-take 3 mystream)
=> (0 1 2)
(my-take 3 mystream)
=> (3 4 5)