Creating a map from a var in Clojure - dictionary

How does one create a map from a var in Clojure?
For example, if one has a var called 'var', as follows:
(def var "I am a var")
And then, one wants to convert it to the following:
{:var "I am a var"}
How does one achieve that?
By the way, just to be clear:
I want the f from (f var) where 'f' converts var to {:var "I am a var"}.

If you want to create a map from the var name to its value you can create a macro:
(defmacro to-map [sym]
`{(keyword '~sym) ~sym})
(to-map var)
=> {:var "I am a var"}

FYI, this macro can take optional args to construct a map.
(defmacro to-map [& vs]
"foo = 1, bar = 2. (to-map foo bar) ==> {:foo 1 :bar 2}"
`(let [ks# (map keyword '~vs)]
(zipmap ks# [~#vs])))

maybe you can do if using the var's metadata:
(defn process-var [param]
{(-> param meta :name keyword)
#param})
in repl:
user> (def x "fourty two")
#'user/x
user> (process-var #'x)
{:x "fourty two"}
user> (def y 1001)
#'user/y
user> (map (comp process-var (ns-interns *ns*)) '(x y))
({:x "fourty two"} {:y 1001})
user> (map (comp process-var (ns-interns *ns*)) '(x y process-var))
({:x "fourty two"} {:y 1001} {:process-var #function[user/process-var]})
also you can take a var straight from namespace's symbols table:
(defn by-name
([name-symbol] (by-name name-symbol *ns*))
([name-symbol ns]
{(keyword name-symbol)
(deref (ns-resolve ns name-symbol))}))
user> (by-name 'x)
{:x "fourty two"}
user> (by-name 'map 'clojure.core)
{:map #function[clojure.core/map]}
user> (by-name '*clojure-version* 'clojure.core)
{:*clojure-version* {:major 1, :minor 8, :incremental 0, :qualifier nil}}

Related

How to "filter" maps properly in Clojure?

I ve been playing around w/ Clojure for a while and I got stuck in something I think very trivial for many...but not me. I've the following piece of code;
;; Define a Record structure
(defrecord Person [first-name last-name age occupation])
(def john (->Person "John" "Frusciante" 50 "Guitarist"))
;; People map
(def people {"1" john
"2" (->Person "Pablo" "Neruda" 90 "Poet")
"3" (->Person "Stefan" "Zweig" 120 "Author")
}
)
(defn get-120-year-old-guy
[peeps]
(filter #(= (:age %) 120) peeps)
)
(println "who's 120?: " (get-120-year-old-guy people))
This call returns an empty list. I know it's something wrong the way I retrieve the value but can't see what is that exactly.
You can get a hint of what's going on by changing the function temporarily:
(defn get-120-year-old-guy
[peeps]
(filter (fn [m] (println (type m) m)) peeps))
Prints:
(clojure.lang.MapEntry [1 #user.Person{:first-name John, :last-name Frusciante, :age 50, :occupation Guitarist}]
clojure.lang.MapEntry [2 #user.Person{:first-name Pablo, :last-name Neruda, :age 90, :occupation Poet}]
clojure.lang.MapEntry [3 #user.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}]
)
Note how each entry is a MapEntry. In your attempt, you're applying :age to the whole MapEntry (which returns nil), not just the person.
I think destructuring using a full anonymous function would be the easiest way:
(defn get-120-year-old-guy
[peeps]
(filter (fn [[_ person]] (= (:age person) 120)) peeps))
Outputs:
who's 120?: ([3 #user.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}])
#leetwinski points out a more idiomatic solution that does away with the explicit function altogether:
(filter (comp #{120} :age val) people)
Broken down:
(defn get-120-year-old-guy [peeps]
(filter (comp ; And (comp)ose all three checks together
#{120} ; Then test if it's in the set of #{120}
:age ; Then get the age
val) ; Get the value from the MapEntry
peeps))
If you look at the first item in your outer map, you'll see that each item is a clojure.lang.MapEntry from string to Person. The key is "1" and the value is a Person record:
> (first people)
["1"
{:first-name "John",
:last-name "Frusciante",
:age 50,
:occupation "Guitarist"}]
To filter on the :age field, you have to first get the value of the {key, value} pair. One way is to use the val function to get that before getting :age from the Person map. Your filter function is then:
(defn get-120-year-old-guy
[peeps]
(filter #(= (:age (val %)) 120) peeps)
)
> (println "who's 120?: " (get-120-year-old-guy people))
who's 120?: ([3 #challenges.anag.Person{:first-name Stefan, :last-name Zweig, :age 120, :occupation Author}])
Another option is to destructure the clojure.lang.MapEntry:
(defn get-120-year-old-guy
[peeps]
(filter (fn [[_ v]] (= (:age v) 120)) peeps)
)
Here you can see (fn [[_ v]] ... where the _ is used as a place holder for the unused key.

Adding values to vectors and sets

Here is what I have:
(def my-atom (atom []))
(defn add-to-my-atom! [x]
(swap! my-atom conj x))
How do I append value to the vector only if it's not present already? I want to be able to use a predicate for the testing. For example in Common Lisp there is pushnew:
pushnew item place &key key test test-not
Is there something similar in Clojure? Perhaps, I should use sets instead of vectors. Fine. How do you define predicate that set will use to compare its values? For example, set can contain strings, and suppose that differences in case should not affect operations on sets, how Clojure deals with that?
Using a vector:
user> (def my-atom (atom []))
(defn push-new
[place item pred]
(if (pred place item)
(conj place item)
place))
(defn add-to-my-atom!
[x]
(swap! my-atom push-new x
(fn [place item]
(not (some #(= (.toLowerCase %)
(.toLowerCase item))
place)))))
#'user/add-to-my-atom!
user> (add-to-my-atom! "Hello World!")
["Hello World!"]
user> (add-to-my-atom! "hello world!")
["Hello World!"]
user> (add-to-my-atom! "ABCDE")
["Hello World!" "ABCDE"]
user> (add-to-my-atom! "abcde")
["Hello World!" "ABCDE"]
Using a set with a custom sorting comparitor:
user> (def my-atom (atom (sorted-set-by (fn [a b] (compare (.toLowerCase a) (.toLowerCase b))))))
#'user/my-atom
user> (swap! my-atom conj "Hello")
#{"Hello"}
user> (swap! my-atom conj "hello")
#{"Hello"}
user> (swap! my-atom conj "abc")
#{"abc" "Hello"}
user> (swap! my-atom conj "Abc")
#{"abc" "Hello"}
If you were not working with atoms, the fn to add to a vector if it is not there would be:
(defn push-new [v value]
(if (some #{value} v)
v
(conj v value)))
Now you can easily use that fn to move from one value of the atom to the next:
(defn add-to-my-atom [the-atom x]
(swap! the-atom push-new x))
Depending on your use case, a set can be more appropriate. Clojure relies on the equals and hashcode implementation of the objects that you put into the set, unless you use a sorted-set-by, or you can simply uppercase them before putting them into the set.

Using Clojure update-in with multiple keys

I'm trying to apply a function to all elements in a map that match a certain key.
(def mymap {:a "a" :b "b" :c "c"})
(update-in mymap [:a :b] #(str "X-" %))
I'm expecting
{:a "X-a", :c "c", :b "X-b"}
But I get
ClassCastException java.lang.String cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:702)
Anyone can help me with this?
update-in is to update a single key in the map (at a particular nesting level, [:a :b] means update key :b inside the map value of key :a.
What you want can be done using reduce:
(reduce #(assoc %1 %2 (str "X-" (%1 %2)))
mymap
[:a :b])
Here's a generalized function:
(defn update-each
"Updates each keyword listed in ks on associative structure m using fn."
[m ks fn]
(reduce #(update-in %1 [%2] fn) m ks))
(update-each mymap [:a :b] #(str "X-" %))
In the solution below, the haspmap if first filtered, then it is mapped to the str function, and then merged with the original hashmap -
(def m {:a "a" :b "b" :c "c"})
(def keys #{:a :b})
(->> m
(filter (fn [[k v]] (k keys)))
(map (fn [[k v]] [k (str "X-" v)]))
(into {})
(merge m))

reverse lookup in a map

I have to extract a key from a map using a value. Is there a way to do this other than implementing reverse lookup myself?
I think that map-invert is the right way to do this.
From the docs:
;; Despite being in clojure.set, this has nothing to do with sets.
user=> (map-invert {:a 1, :b 2})
{2 :b, 1 :a}
;; If there are duplicate keys, one is chosen:
user=> (map-invert {:a 1, :b 1})
{1 :b}
;; I suspect it'd be unwise to depend on which key survives the clash.
You can reverse a map really easily with a 2-line function:
(defn reverse-map [m]
(into {} (map (fn [[a b]] [b a]) m)))
(def a {:a 1 :b 2 :c 3})
(reverse-map a)
=> {1 :a, 3 :c, 2 :b}
((reverse-map a) 1)
=> :a
Try
(some #(if (= (val %) your-val) (key %)) your-map)
If you are using ClojureScript or you need one more alternative :)
(zipmap (vals m) (keys m))
Another one:
(defn reverse-map [m]
(apply hash-map (mapcat reverse m)))
(defn reverse-lookup [m k]
(ffirst (filter (comp #{k} second) m)))
if you want to keep the keys, it is better to just invert the map, but collect the old keys in a set / list etc...
(defn map-inverse [m]
(reduce (fn [m' [k v]] (update m' v clojure.set/union #{k})) {} m))
(defn map-inverse [m]
(reduce (fn [m' [k v]] (update m' v conj k)) {} m))

Merge list of maps and combine values to sets in Clojure

What function can I put as FOO here to yield true at the end? I played with hash-set (only correct for first 2 values), conj, and concat but I know I'm not handling the single-element vs set condition properly with just any of those.
(defn mergeMatches [propertyMapList]
"Take a list of maps and merges them combining values into a set"
(reduce #(merge-with FOO %1 %2) {} propertyMapList))
(def in
(list
{:a 1}
{:a 2}
{:a 3}
{:b 4}
{:b 5}
{:b 6} ))
(def out
{ :a #{ 1 2 3}
:b #{ 4 5 6} })
; this should return true
(= (mergeMatches in) out)
What is the most idiomatic way to handle this?
This'll do:
(let [set #(if (set? %) % #{%})]
#(clojure.set/union (set %) (set %2)))
Rewritten more directly for the example (Alex):
(defn to-set [s]
(if (set? s) s #{s}))
(defn set-union [s1 s2]
(clojure.set/union (to-set s1) (to-set s2)))
(defn mergeMatches [propertyMapList]
(reduce #(merge-with set-union %1 %2) {} propertyMapList))
I didn't write this but it was contributed by #amitrathore on Twitter:
(defn kv [bag [k v]]
(update-in bag [k] conj v))
(defn mergeMatches [propertyMapList]
(reduce #(reduce kv %1 %2) {} propertyMapList))
I wouldn't use merge-with for this,
(defn fnil [f not-found]
(fn [x y] (f (if (nil? x) not-found x) y)))
(defn conj-in [m map-entry]
(update-in m [(key map-entry)] (fnil conj #{}) (val map-entry)))
(defn merge-matches [property-map-list]
(reduce conj-in {} (apply concat property-map-list)))
user=> (merge-matches in)
{:b #{4 5 6}, :a #{1 2 3}}
fnil will be part of core soon so you can ignore the implementation... but it just creates a version of another function that can handle nil arguments. In this case conj will substitute #{} for nil.
So the reduction conjoining to a set for every key/value in the list of maps supplied.
Another solution contributed by #wmacgyver on Twitter based on multimaps:
(defn add
"Adds key-value pairs the multimap."
([mm k v]
(assoc mm k (conj (get mm k #{}) v)))
([mm k v & kvs]
(apply add (add mm k v) kvs)))
(defn mm-merge
"Merges the multimaps, taking the union of values."
[& mms]
(apply (partial merge-with union) mms))
(defn mergeMatches [property-map-list]
(reduce mm-merge (map #(add {} (key (first %)) (val (first %))) property-map-list)))
This seems to work:
(defn FOO [v1 v2]
(if (set? v1)
(apply hash-set v2 v1)
(hash-set v1 v2)))
Not super pretty but it works.
(defn mergeMatches [propertyMapList]
(for [k (set (for [pp propertyMapList] (key (first pp))))]
{k (set (remove nil? (for [pp propertyMapList] (k pp))))}))

Resources