How to generate this expression in Clojure? - clojure

I am using lobos to create a set of tables:
(create
(table :users
(integer :id 20)
(varchar :name 100)))
The schema of a table is kept in a seq:
([:integer :id 20] [:varchar :name 100])
How can I generate that expression with the seq? I find clojure.contrib/apply-macro may be used, but are there other ways?

You can use the following macro:
(defmacro table-create [name coll]
`(~'create
(~'table ~name ~#(map (fn [r]
(let [[type c v] r]
(list (symbol (subs (str type) 1)) c v)))
coll))))
Here is a sample run:
(macroexpand-1 '(table-create :users ([:integer :id 20] [:varchar :name 100])))
;=> (create
(table :users
(integer :id 20)
(varchar :name 100)))

Related

Transforming a map with assoc and dissoc at the same time in clojure

I have a map as seen bellow. I am getting the information from a datomic database. Now I want to transform the data structure here:
(def my-map [{:db/id #object[Object 56536887900242005],
:height 630,
:distance 1474.1,
:coordinates [-26.65622109697031 30.48401767312403],
:location #:location{:id 1}}
{:db/id #object[Object 56536887900242006],
:height 22075,
:distance 1503.2,
:coordinates [-26.65622109697031 30.48401767312403],
:location #:location{:id 2}}
{:db/id #object[Object 56536887900242007],
:height 24248,
:distance 1695.6,
:coordinates [-26.662030943549 30.25648873549992],
:location #:location{:id 3}})
to look like this
{1 {:height 630, :distance 1474.1,:coordinates [-26.65622109697031 30.48401767312403]}
2 {:height 22075, :distance 1503.2,:coordinates [-26.65622109697031 30.48401767312403]}
3 {:height 24248, :distance 1695.6,:coordinates [-26.65622109697031 30.48401767312403]}}
I want to pull the 1 from #:location{:id 1} which I will then assoc with
{:height 22075, :distance 1503.2,:coordinates [-26.65622109697031 30.48401767312403]}
Bellow I have code that return the above, but I do not know how to assoc it into the :id and I also do not know how to get the id seeing that the data has a #
(map #(dissoc % :db/id :location ) my-map)
I use plumbing.core in every project anyway, and if you do as well, then this solution might appeal to you.
(grouped-map
(fn-> :location :location/id)
(fn-> (dissoc :location :db/id))
my-map)
You can write like this:
(into {}
(map
#(hash-map
(get-in % [:location :location/id])
(dissoc % :db/id :location))
my-map))
Sometimes using for can help make the structure of your data more apparent:
(->> (for [record my-map]
[(-> record :location :location/id)
(dissoc record :db/id :location)])
(into {}))
The following function will do the trick:
(defn transform [coll]
(->> coll
(map #(hash-map
(-> % :location :location/id)
(dissoc % :db/id :location)))
(into {})))

What is the idiomatic way to alter a vector that is stored in an atomized map?

I have an atom called app-state that holds a map. It looks like this:
{:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]}
What is the idiomatic way to remove the element inside the vector with :id = 2 ? The result would look like:
{:skills [{:id 1 :text "hi"}]}
...
So far, I have this:
(defn new-list [id]
(remove #(= id (:id %)) (get #app-state :skills)))
swap! app-state assoc :skills (new-list 2)
It works, but I feel like this isn't quite right. I think it could be something like:
swap! app-state update-in [:skills] remove #(= id (:id %))
But this doesn't seem to work.
Any help is much appreciated!
Try this:
(defn new-list [app-state-map id]
(assoc app-state-map :skills (into [] (remove #(= id (:id %)) (:skills app-state-map)))))
(swap! app-state new-list 2)
swap! will pass the current value of the atom to the function you supply it. There's no need to dereference it yourself in the function.
See the docs on swap! for more details.
(swap! state update :skills (partial remove (comp #{2} :id)))
(def skills {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
(defn remove-skill [id]
(update skills :skills (fn [sks] (vec (remove #(= id (:id %)) sks)))))
You would then be able to call say (remove-skill 1) and see that only the other one (skill with :id of 2) is left.
I like your way better. And this would need to be adapted for use against an atom.
You can use filter to do this. Here is a function that takes an id and the map and let's you filter out anything that doesn't match your criteria. Of course, you can make the #() reader macro check for equality rather than inequality depending on your needs.
user=> (def foo {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
#'user/foo
user=> (defn bar [id sklz] (filter #(not= (:id %) id) (:skills sklz)))
#'user/bar
user=> (bar 1 foo)
({:id 2, :text "yeah"})
user=> (bar 2 foo)
({:id 1, :text "hi"})

Accessing argument's metadata in Clojure macro

Is there a way to retrieve the metadata of the arguments inside a clojure macro without using eval? The only thing I could come up with so far is this:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (eval `(meta (var ~s)))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
I ended up finding a solution:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (meta (resolve s))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
So the key part here is to use resolve function to get the var associated to the symbol.

ClassCastException when filtering a set by another set

Here is a code example from Clojure Programming
(defn character
[name & {:as opts}]
(ref (merge {:name name :itmes #{} :health 500}
opts)))
(def smaug (character "Smaug" :health 500 :strength 400 :items (set (range 50))))
(def bilbo (character "Bilbo" :health 100 :strength 100))
(def gandalf (character "Gandalf" :health 75 :mana 750))
(defn loot
[from to]
(dosync
(when-let [item (first (:items #from))]
(alter to update-in [:items] conj item)
(alter from update-in [:items] disj item))))
(wait-futures 1
(while (loot smaug bilbo))
(while (loot smaug gandalf)))
(map (comp count :items deref) [bilbo gandalf])
(filter (:items #bilbo) (:items #gandalf))
Everything works fine until the last line, which brings up an error:
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn clojure.core/filter/fn--4264 (core.clj:2605)
The constructor function "character" typos the key for :items as :itmes, making the conj on alter get nil as the initial value. Conjing nil and a value gives you a list -- thus the ClassCastException.
Looks like you've got a typo in your definition of character - :itmes instead of :items.
This means that when you loot, you're calling
(conj nil 0), which turns the entry under :items into a list of (0). A list doesn't implement IFn, hence the error.

Filter elements not matching on a key between two lists in clojure

I have two vectors of maps in clojure
(def a [{:name "batman" :universe "DC" :email "batman#wayne.com"}
{:name "flash" :universe "DC" :email "flash#speedfreak.com"}
{:name "thor" :universe "MARVEL" :email "thor#asgard.com"}])
(def b [{:name "batman" :universe "DC" :email "batman#wayne.com"}
{:name "flash" :universe "DC" :email "flash1#speedfreak.com"}
{:name "thor" :universe "MARVEL" :email "thor#asgard.com"}
{:name "riddler" :universe "DC" :email "riddler#whoami.com"}])
The :name property in both list are always in sync; i.e., batman in a is always batman on b too.
What I want to do, though, is to pick out only rows where email is not matching up.
(stuck-on-what-to-write-here)
=> ({:name "flash", :universe "DC", :email "flash1#speedfreak.com"})
If I filter out the rows with
(filter #(not (contains? (set (map :email a)) (:email %))) b)
it returns 2 rows, one with flash as it doesn't match and one with riddler because.. well, it's not there in a and hence doesn't match!
What do I need to do to get just flash and not riddler?
(defn mismatch?
"Returns true if there is any mismatch between corresponding items."
[a b]
(= (count (clojure.set/union (set a) (set b)))
(max (count a) (count b))))
If you want a specific name, you could use list comprehension:
(defn get-mismatched-emails
"Returns the name of any superheroes with inconsistent contact records."
[a b]
(for [i a j b
:when (and (= (:name i) (:name j))
(not= (:email i) (:email j)))]
(:name i)))
Note that this function is rather inefficient, as it has to compare every combination of pairs between the two lists. Simply by changing your data structure to a map of maps:
{"batman" {:universe "DC" :email "batman#wayne.com"}
"flash" {:universe "DC" :email "flash#speedfreak.com"}
"thor" {:universe "MARVEL" :email "thor#asgard.com"}}
you would rather easily be able to scale what you want up to much larger datasets.
(for [name (clojure.set/union (set (keys a))
(set (keys b)))
:when (detect-mismatched-data (a name) (b name))]
name)
One possible way is to add a second condition to your filter on the email. I have no idea about the performance over galdre's answer though!
(def a-names (set (map :name a)))
(def a-emails (set (map :email a)))
(filter #(and
(contains? a-names (:name %))
(not (contains? a-emails (:email %))))
b)
this will output ({:name "flash", :universe "DC", :email "flash1#speedfreak.com"})
Also not that I've put (set (map :name a)) outside filter so it doesn't have to loop through to collect names for each item in b.
Try:
(filter #(not (contains? (set (map :email b)) (:email %))) a)
Returns:
({:universe "DC", :name "flash", :email "flash#speedfreak.com"})