How to search and replace in a Clojure script data structure? - clojure

I would like to have a search and replace on the values only inside data structures:
(def str [1 2 3
{:a 1
:b 2
1 3}])
and
(subst str 1 2)
to return
[2 2 3 {:a 2, :b 2, 1 3}]
Another example:
(def str2 {[1 2 3] x, {a 1 b 2} y} )
and
(subst str2 1 2)
to return
{[1 2 3] x, {a 1 b 2} y}
Since the 1's are keys in a map they are not replaced

One option is using of postwalk-replace:
user> (def foo [1 2 3
{:a 1
:b 2
1 3}])
;; => #'user/foo
user> (postwalk-replace {1 2} foo)
;; => [2 2 3 {2 3, :b 2, :a 2}]
Although, this method has a downside: it replaces all elements in a structure, not only values. This may be not what you want.
Maybe this will do the trick...
(defn my-replace [smap s]
(letfn [(trns [s]
(map (fn [x]
(if (coll? x)
(my-replace smap x)
(or (smap x) x)))
s))]
(if (map? s)
(zipmap (keys s) (trns (vals s)))
(trns s))))
Works with lists, vectors and maps:
user> (my-replace {1 2} foo)
;; => (2 2 3 {:a 2, :b 2, 1 3})
...Seems to work on arbitrary nested structures too:
user> (my-replace {1 2} [1 2 3 {:a [1 1 1] :b [3 2 1] 1 1}])
;; => (2 2 3 {:a (2 2 2), :b (3 2 2) 1 2})

Related

What is the difference between set and hash-set in Clojure?

I can't find an explanation on the documentation nor on the web for why there are two different functions that seem to do practically the same thing, apart from accepting one a collection and the other one a list of arguments (but this could be easily solved using (apply hash-set coll)).
Just checked the source code for set and hash-set. You are right that there is practically no difference, aside from one accepting multiple arguments and the other accepting a collection.
Here is the source, by the way:
For set
For hash-set
It is just for convenience. Same with vector vs vec. However it is not completely parallel for maps and lists:
(vector 0 1 2) => [0 1 2]
(apply vector (range 3)) => [0 1 2]
(vec (range 3)) => [0 1 2]
(hash-set 0 1 2) => #{0 1 2}
(apply hash-set (range 3)) => #{0 1 2}
(set (range 3)) => #{0 1 2}
(hash-map :a 1 :b 2) => {:b 2, :a 1}
(apply hash-map [:a 1 :b 2]) => {:b 2, :a 1}
(into {} [[:a 1] [:b 2]]) => {:a 1, :b 2}
(list 0 1 2) => (0 1 2)
(apply list (range 3)) => (0 1 2)
(into (list) (range 3)) => (2 1 0) ; *** reversed order ***
Since we can define each in terms of the other:
(defn hash-set [& args]
(clojure.core/set args))
or
(defn set [coll]
(apply clojure.core/hash-set coll))
... it is likely that both are defined separately for speed.

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 programmatically namespace map keys

I recently learned about namespaced maps in clojure.
Very convenient, I was wondering what would be the idiomatic way of programmatically namespacing a map? Is there another syntax that I am not aware of?
;; works fine
(def m #:prefix{:a 1 :b 2 :c 3})
(:prefix/a m) ;; 1
;; how to programmatically prefix the map?
(def m {:a 1 :b 2 :c 3})
(prn #:prefix(:foo m)) ;; java.lang.RuntimeException: Unmatched delimiter: )
This function will do what you want:
(defn map->nsmap
[m n]
(reduce-kv (fn [acc k v]
(let [new-kw (if (and (keyword? k)
(not (qualified-keyword? k)))
(keyword (str n) (name k))
k) ]
(assoc acc new-kw v)))
{} m))
You can give it an actual namespace object:
(map->nsmap {:a 1 :b 2} *ns*)
=> #:user{:a 1, :b 2}
(map->nsmap {:a 1 :b 2} (create-ns 'my.new.ns))
=> #:my.new.ns{:a 1, :b 2}
Or give it a string for the namespace name:
(map->nsmap {:a 1 :b 2} "namespaces.are.great")
=> #:namespaces.are.great{:a 1, :b 2}
And it only alters keys that are non-qualified keywords, which matches the behavior of the #: macro:
(map->nsmap {:a 1, :foo/b 2, "dontalterme" 3, 4 42} "new-ns")
=> {:new-ns/a 1, :foo/b 2, "dontalterme" 3, 4 42}
Here is another example inspired by https://clojuredocs.org/clojure.walk/postwalk#example-542692d7c026201cdc327122
(defn map->nsmap
"Apply the string n to the supplied structure m as a namespace."
[m n]
(clojure.walk/postwalk
(fn [x]
(if (keyword? x)
(keyword n (name x))
x))
m))
Example:
(map->nsmap {:my-ns/a 1 :my-ns/b 2 :my-ns/c 3} "your-ns")
=> #:your-ns{:a 1, :b 2, :c 3}

Combining vectors by index

I am looking to write a function which inputs two vectors of length n,
i.e. [:a :b :c :d :e :f] [1 2 3 4 5 6].
Outputting one vector of length 2n
[:a 1 :b 2 :c 3 :d 4 :e 5 :f 6].
However, if the second vector being input doesn't match the length of n it will cycle,
i.e. [:a :b :c :d :e :f] [1 2 3]
outputs: [:a 1 :b 2 :c 3 :d 1 :e 2 :f 3].
(defn Swanson [x y] (vec (flatten (interleave x (repeat (count x) y)))))
Moreover, the function can also take [x y min max n], where x and y are vectors, min is an index to start the interleaving, max is an index to end the interleaving, and n is a step-size for the interleaving.
You want cycle:
user> (take 6 (cycle [1 2 3]))
(1 2 3 1 2 3)
user> (interleave [:a :b :c :d :e :f] (cycle [1 2 3]))
(:a 1 :b 2 :c 3 :d 1 :e 2 :f 3)
With x and y vectors, min the (inclusive) starting index, max the (exclusive) ending index, n the step size:
(defn swanson [x y min max n]
(->> (interleave x (cycle y))
(take max)
(drop min)
(take-nth n)))
You can use the interleave function from the seq library for that:
=> (interleave [:a :b :c :d :e :f] [1 2 3 4 5 6])
(:a 1 :b 2 :c 3 :d 4 :e 5 :f 6)
Hope this helps!
for two any size vectors:
(defn cycleave [a b]
(let [c (max (count a) (count b))]
(take (* 2 c) (interleave (cycle a)
(cycle b)))))
will give:
user => (cycleave [:a :b :c :d :e :f] [1 2 3])
(:a 1 :b 2 :c 3 :d 1 :e 2 :f 3)

How to reduce this collection?

I am struggling with the following problem...
Given a collection of maps
[
{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}
]
want to reduce/transform to...
{
1 {:b [1 2] :c [1 2] :d [1 2 3]}
2 {:b [1 2 3] :c 1 :d [5 6 7]}
}
group-by :a (primary key) and accumulate the distinct values for other keys.
I can do this in a brute force/imperative way, but struggling to figure out how to solve this in clojure way.
Thanks
Here is an admittedly inelegant, first-draft solution:
(defn reducing-fn [list-of-maps grouping-key]
(reduce (fn [m [k lst]]
(assoc m k (dissoc (reduce (fn [m1 m2]
(apply hash-map
(apply concat
(for [[k v] m2]
[k (conj (get m1 k #{}) v)]))))
{}
lst)
grouping-key)))
{}
(group-by #(grouping-key %) list-of-maps)))
user> (reducing-fn [{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}]
:a)
=> {2 {:c #{1}, :b #{1 2 3}, :d #{5 6 7}}, 1 {:c #{1 2}, :b #{1 2}, :d #{1 2 3}}}
Will try and figure out a more polished approach tomorrow, heading off to bed right now :)
(use 'clojure.set)
(def data
[
{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}
]
)
(defn key-join
"join of map by key , value is distinct."
[map-list]
(let [keys (keys (first map-list))]
(into {} (for [k keys] [k (vec (set (map #(% k) map-list)))]))))
(defn group-reduce [key map-list]
(let [sdata (set map-list)
group-value (project sdata [key])]
(into {}
(for [m group-value] [(key m) (key-join (map #(dissoc % key) (select #(= (key %) (key m)) sdata)))]))))
;;other version fast than group-reduce
(defn gr [key map-list]
(let [gdata (group-by key map-list)]
(into {} (for [[k m] gdata][k (dissoc (key-join m) key)]))))
user=> (group-reduce :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}
user=> (gr :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}
Another solution:
(defn pivot [new-key m]
(apply merge
(for [[a v] (group-by new-key m)]
{a (let [ks (set (flatten (map keys (map #(dissoc % new-key) v))))]
(zipmap ks (for [k ks] (set (map k v)))))})))
ETA: new-key would be the :a key here and m is your input map.
The first "for" destructures the group-by. That's where you're partitioning the data by the input "new-key." "for" generates a list - it's like Python's list comprehension. Here we're generating a list of maps, each with one key, whose value is a map. First we need to extract the relevant keys. These keys are held in the "ks" binding. We want to accumulate distinct values. While we could do this using reduce, since keywords are also functions, we can use them to extract across the collection and then use "set" to reduce down to distinct values. "zipmap" ties together our keys and their associated values. Then outside the main "for," we need to convert this list of maps into a single map whose keys are the distinct values of "a".
Another solution:
(defn transform
[key coll]
(letfn [(merge-maps
[coll]
(apply merge-with (fnil conj #{}) {} coll))
(process-key
[[k v]]
[k (dissoc (merge-maps v) key)])]
(->> coll
(group-by #(get % key))
(map process-key)
(into (empty coll)))))
Code untested, though.
EDIT: Of course it doesn't work, because of merge-with trying to be too clever.
(defn transform
[key coll]
(letfn [(local-merge-with
[f m & ms]
(reduce (fn [m [k v]] (update-in m [k] f v))
m
(for [m ms e m] e)))
(merge-maps
[coll]
(apply local-merge-with (fnil conj #{}) {} coll))
(process-key
[[k v]]
[k (dissoc (merge-maps v) key)])]
(->> coll
(group-by #(get % key))
(map process-key)
(into (empty coll)))))