Combining vectors by index - clojure

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)

Related

Selecting vector elements from dynamic indices using Specter

I have a vector [:a :b :c :d :e] and some indices [1 2 4].
Using Specter, how to select the elements of my vector from the indices, so that it returns [:b :c :e]?
No need to use Specter:
(let [a [:a :b :c :d :e]
B [1 2 4]]
(mapv (partial nth a) B))
Or even simpler:
(let [a [:a :b :c :d :e]
B [1 2 4]]
(mapv a B))
But if you insist on using Specter, then here it is:
(let [a [:a :b :c :d :e]
B [1 2 4]]
(select (apply multi-path B) a))

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)

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

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})

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)))))

In Clojure can I call a function that expects keys with a map?

Let's say I have
(defn test [ & {:keys [a b c]}]
(println a)
(println b)
(println c))
What I want is to call test with a map {:a 1 :b 2 :c 3}.
This works:
(apply test [:a 1 :b 2 :c 3])
These do not:
(apply test {:a 1 :b 2 :c 3})
(apply test (seq {:a 1 :b 2 :c 3}))
EDIT
So you can of course define the function like this also:
(defn test [{:keys [a b c]}] ; No &
(println a)
(println b)
(println c))
And then you can pass a map to it:
(test {:a 1 :b 2 :c 3})
1
2
3
When learning clojure I had missed this was possible. Nevertheless if you ever come across a function defined by me or somebody like me then knowing how to pass a map to it could still be useful ;)
user> (apply list (mapcat seq {:a 1 :b [2 3 4]}))
(:a 1 :b [2 3 4])
Any good reason not to define it like this in the first place?
(defn my-test [{:keys [a b c]}] ;; so without the &
(println a)
(println b)
(println c))
and then call it like this?
(my-test {:a 10 :b 20 :c 30})
which outputs:
10
20
30
nil
This works, but is inelegant:
(apply test (flatten (seq {:a 1 :b 2 :c 3})))
The reason (apply test (seq {:a 1 :b 2 :c 3})) doesn't work is that (seq {:a 1 :b 2 :c 3}) returns [[:a 1] [:b 2] [:c 3]], flatten takes care of this.
Better solutions?