What does Clojure's zip-map do? - clojure

I am new at Clojure and I needed help with this function. If you could please tell me what this function does and how it works I would be really thankfull.
(defn zip-map
[k v]
(into{} (map vec (partition 2 (interleave k v)))))

Example of usage:
(zip-map [:a :b :c] [1 2 3]) ;=> {:a 1, :b 2, :c 3}
And from the inside out:
(interleave [:a :b :c] [1 2 3]) ;=> (:a 1 :b 2 :c 3)
(partition 2 '(:a 1 :b 2 :c 3)) ;=> ((:a 1) (:b 2) (:c 3))
(map vec '((:a 1) (:b 2) (:c 3))) ;=> ([:a 1] [:b 2] [:c 3])
(into {} '([:a 1] [:b 2] [:c 3])) ;=> {:a 1, :b 2, :c 3}

The function is more complicated hence harder to understand than it need be. It could be written thus:
(defn zip-map [ks vs]
(into {} (map vector ks vs)))
when
(zip-map [:a :b :c] [1 2 3])
;{:a 1, :b 2, :c 3}
as before.
The function imitates the standard zipmap, which you can find explained, complete with source code, in the official docs or ClojureDocs, which also gives examples. Both these sites help you to pick your way through the Clojure vocabulary.
As is often the case, the standard function is faster though more complex than the simple one-liner above.

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

zipmap with multi-value keys

The following:
(zipmap '(:a :b :c :c) '(1 2 3 4))
evals to: {:c 4, :b 2, :a 1}
I would like to get:
{:c '(3 4) :b '(2) :a '(1)}
instead.
How should I define my own zipmap that takes two lists and returns a map with multiple values for keys?
This will do
(defn zippy [l1 l2]
(apply merge-with concat (map (fn [a b]{a (list b)}) l1 l2)))
;;; ⇒ #'user/zippy
(zippy '(:a :b :c :c) '(1 2 3 4))
;;; ⇒ {:c (3 4), :b (2), :a (1)}

Is there a standard func that takes a dict and a list of keys and returns the list of corresponding vals?

I'm looking for something similar to select-keys:
(desired-fn {:a 1, :b 2, :c 3, :d 4} [:a :d])
;= [1 4]
;; N.B. the order of the keys in the argument seq is preserved
(= (desired-fn (array-map :a 1, :b 2, :c 3, :d 4)
[:b :c])
(desired-fn (array-map :d 4, :c 3, :a 1, :b 2)
[:b :c]))
;= true
It's not particularly hard to implement, though I haven't tried to come up with a good name yet:
(defn select-values-corresponding-to-keys [m ks]
(for [k ks]
(get m k)))
Am I ignorant of a standard function that meets precisely this need? If not, do other languages —e.g., Python, Ruby, Haskell— have a name for this function?
Maps are functions which operate on their keys:
({:a 1, :b 2} :a)
;=> 1
(map {:a 1, :b 2, :c 3, :d 4} [:a :d])
;=> (1 4)
(= (map (array-map :a 1, :b 2, :c 3, :d 4)
[:b :c])
(map (array-map :d 4, :c 3, :a 1, :b 2)
[:b :c]))
;=> true
If you want the result as a vector, just use vec or into [] ..., or replace map with mapv.
Keywords are themselves functions (they implement IFn) and they can look themselves into a map and return the value so one option would be to use juxt:
(def keys-to-vals (juxt :b :c))
(= (keys-to-vals {:a 1, :b 2, :c 3, :d 4})
(keys-to-vals {:d 4, :c 3, :a 1, :b 2}))
So basically your desired fn now becomes:
(defn select-vals [map keys] ((apply juxt keys) map))
map is the function you are looking for:
(map {:a 1 :b 2 :c 3} [:a :c])
=> (1 3)
This works because the hashmap itself works as a function (i.e. implements clojure.lang.IFn) that returns the value for any key that it is given.
user=> ((juxt :a :c) {:a 1 :b 2 :c 3})
[1 3]
Jay Fields explores this function and a couple other related ones in an insightful blog post # http://blog.jayfields.com/2011/01/clojure-select-keys-select-values-and.html.
(I found that by accident just a few minutes ago when I searched for "select-keys".)
I'd still like to know if there's a "canonical" implementation somewhere, so I'm leaving the question as open.

How do I conj to a clojure vector conditionally

Is there a cleaner way to do something like the following in clojure?
(defn this [x] (* 2 x))
(defn that [x] (inc x))
(defn the-other [x] (-> x this that))
(defn make-vector [thing]
(let [base (vector (this (:a thing))
(that (:b thing)))]
(if-let [optional (:c thing)]
(conj base (the-other optional))
base)))
(make-vector {:a 1, :b 2}) ;=> [2 3]
(make-vector {:a 1, :b 2, :c 3}) ;=> [2 3 7]
By "cleaner" I mean something closer to this:
(defn non-working-make-vector [thing]
(vector (this (:a thing))
(that (:b thing))
(if (:c thing) (the-other (:c thing)))))
(non-working-make-vector {:a 1, :b 2} ;=> [2 3 nil] no nil, please!
(non-working-make-vector {:a 1, :b 2, :c 3} ;=> [2 3 7]
Note that I might want to call some arbitrary function (e.g. this, that, the-other) on any of the keys in thing and place the result in the returned vector. The important thing is that if the key doesn't exist in the map it should not put a nil in the vector.
This is similar to this question but the output is a vector rather than a map so I can't use merge.
(defn this [x] (* 2 x))
(defn that [x] (inc x))
(defn the-other [x] (-> x this that))
(def k-f-map {:a this
:b that
:c the-other})
(def m1 {:a 1 :b 2})
(def m2 {:a 1 :b 2 :c 3})
(defn make-vector [k-f-map m]
(reduce (fn [res [fk fv]]
(if (fk m)
(conj res (fv (fk m)))
res))
[] k-f-map))
(make-vector k-f-map m1)
-> [2 3]
(make-vector k-f-map m2)
-> [2 3 7]
;;; replace [:a :b :c] with a vector of arbitrary functions
;;; of your choice, or perhaps accept a seqable of functions
;;; as an extra argument
(defn make-vector [thing]
(into [] (keep #(% thing) [:a :b :c])))
;;; from the REPL:
(make-vector {:a 1 :b 2})
; => [1 2]
(make-vector {:a 1 :b 2 :c 3})
; => [1 2 3]
Note that keep only throws out nil; false will be included in the output.
or using cond->?
your make-vector function in cond-> version:
(defn make-vector [thing]
(cond-> [(this (:a thing))
(that (:b thing))]
(:c thing) (conj (the-other (:c thing)))))
you can have more conditions or change :a and :b to be optional as well.

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