Update values of keys in a sequence of maps from the values of first map's keys - clojure

I have a sequence of maps like this:
({:autoNo "1" :title "Title1" :47652 ("name1") :47653 ("name2" "name3")} {:autoNo nil :title nil :47652 nil :47653 nil})
Only the first map contains the values for file-upload-field keys :47652 and :47653 whereas the value of same keys in the second map is nil. What I want is file-upload-field keys having only one name of file value per map like this:
({:autoNo "1" :title "Title1" :47652 "name1" :47653 "name2"} {:autoNo nil :title nil :47652 nil :47653 "name3"})
You can see above as :47652 have only one value in the seq so its value get updated whereas the value of the same key in the next map remains nil. For :47653 having multiple values, the key in first map contains the first value i.e. "name2" whereas the same key in the second map of the sequence contains the next value i.e. "name3".
To achieve so I have created a function like this:
(defn update-file-upload-field-values
[conversation-instances]
(let [file-field-ids [:47652 :47653]]
(first (map (fn [x]
(map
#(update %1 x (fn [z] (str %2)))
conversation-instances ((first conversation-instances) x)))
file-field-ids))))
where conversation-instances is the same sequence of maps stated at the beginning.
My solution is not working as expected. It is returning this:
({:autoNo "1" :title "Title1" :47652 "name1" :47653 ("name2" "name3")})
Which is different from the expected output shown at the beginning.

You call map with two sequences (conversation-instances and the result of ((first conversation-instances) x)), so the result will be as long as the shortest of them. I also think you can replace update with assoc.
Two nested maps with first also create some result you probably don't want. I suggest you to write your functions step by step, testing each increment in REPL, so you know whether your function does what you expect.
You can use reduce to solve this. My update-maps is similar to your inner map, I just added reduce to repeat it for each given key:
(def data '({:autoNo "1" :title "Title1" :47652 ("name1") :47653 ("name2" "name3")}
{:autoNo nil :title nil :47652 nil :47653 nil}))
(defn update-maps [coll k]
(let [values-seq (k (first coll))]
(mapv #(assoc %1 k %2)
coll
(concat values-seq (repeat nil)))))
(defn keys-fn [coll]
(let [ks [:47652 :47653]]
(reduce update-maps
coll
ks)))
(keys-fn data)
=>
[{:autoNo "1", :title "Title1", :47652 "name1", :47653 "name2"} {:autoNo nil, :title nil, :47652 nil, :47653 "name3"}]

(require '[com.rpl.specter :as s])
(use '[plumbing.core])
(let [data '({:autoNo "1" :title "Title1" :47652 ("name1") :47653 ("name2" "name3")}
{:autoNo nil :title nil :47652 nil :47653 nil})]
(->> data
(s/transform (s/subselect [s/ALL (s/keypath :47652) (s/nil->val [nil])]) aconcat)
(s/transform (s/subselect [s/ALL (s/keypath :47653) (s/nil->val [nil])]) aconcat)))

basically, what you could do is something like this:
(map assoc data
(repeat :47652)
(concat (:47652 (first data)) (repeat nil))
(repeat :47653)
(concat (:47653 (first data)) (repeat nil)))
;;=> ({:autoNo "1", :title "Title1", :47652 "name1", :47653 "name2"}
;; {:autoNo nil, :title nil, :47652 nil, :47653 "name3"})
to get rid of repetition you can do something like that:
(defn ks-vs [[k vs]]
[(repeat k) (concat vs (repeat nil))])
(defn process [[head :as data]]
(->> (select-keys head [:47652 :47653])
(mapcat ks-vs)
(apply map assoc data)))
(process data)
;;=> ({:autoNo "1", :title "Title1", :47652 "name1", :47653 "name2"}
;; {:autoNo nil, :title nil, :47652 nil, :47653 "name3"})
which is basically more abstract variant based on the same approach.

Related

GET returning only true params (Clojure)

I'm trying to return 'profile' that were created with :public-profile true.
This is my schema:
(s/defschema profile
{:id s/Int
:name s/Str
:last-name s/Str
:age s/Int
:origin {:country s/Str
:city s/Str}
:public-profile Boolean
})
And this is my GET:
:get {
:responses {http-status/ok {:schema [profile]}}
:handler (fn [_] (ok (vals #profiles)))}}))
I'm kind of newbie programmer on Clojure and I would like to know how to make my get return only public-profile that were true. Just looking for a simple function to make it happen...
Assuming #profiles is a map where values are profiles, then you can return only profiles where :public-profile is true with filter. For example:
(filter
(fn [profile] (:public-profile profile))
(vals #profiles))
In clojure keywords are actually functions and return the value corresponding to them when applied to maps, so a more concise (and I believe idiomatic) way of writing this would be:
(filter :public-profile (vals #profiles))

How to get the href attribute value using enlive

I am a new to Clojure and enlive.
I have html like this
<SPAN CLASS="f10">....</SPAN></DIV><DIV CLASS="p5"><SPAN CLASS="f10">.....</SPAN>
I tried this
(html/select (fetch-url base-url) [:span.f10 [:a (html/attr? :href)]]))
but it returns this
({:tag :a,
:attrs
{:target "detail",
:title
"...",
:href
"value1"},
:content ("....")}
{:tag :a,
:attrs
{:target "detail",
:title
"....",
:href
"value2"},
:content
("....")}
What i want is just value1 and value 2 in the output. How can i accomplish it ?
select returns the matched nodes, but you still need to extract their href attributes. To do that, you can use attr-values:
(mapcat #(html/attr-values % :href)
(html/select (html/html-resource "sample.html") [:span.f10 (html/attr? :href)]))
I use this little function because the Enlive attr functions don't return the values. You are basically just walking the hash to get the value.
user=> (def data {:tag :a, :attrs {:target "detail", :title "...", :href "value1"}})
#'user/data
user=> (defn- get-attr [node attr]
#_=> (some-> node :attrs attr))
#'user/get-attr
user=> (get-attr data :href)
"value1"

Filtering a list of maps in clojure with potentially different keys

Say I have a list of maps that looks like the following:
(def my-map '({:some-key {:another-key "val"}
:id "123"}
{:some-key {:another-key "val"}
:id "456"}
{:some-other-key {:a-different-key "val2"}
:id "789"})
In my attempt to filter this map by :another-key, I tried this:
(filter #(= "val" ((% :some-key) :another-key)) my-map)))
However, this will throw a NullPointerException on the map entry that doesn't contain the key I'm filtering on. What would be the optimal way to filter this map, excluding entries that don't match the filtered schema entirely?
Your first lookup of the key :some-key will return nil if the map key is not in the map. Calling nil will result in the NPE you see.
The solution is easy, just make the keyword lookup itself in the map which work even if given a nil:
(def my-map '({:some-key {:another-key "val"}
:id "123"}
{:some-key {:another-key "val"}
:id "456"}
{:some-other-key {:a-different-key "val2"}
:id "789"}))
(filter #(= "val" (:another-key (% :some-key))) my-map)
You can also use get-in:
(filter #(= "val" (get-in % [:some-key :another-key])) my-map)
And if your list could potentially have nil items:
(filter #(= "val" (:another-key (:some-key %))) my-map)
Explanation:
(:k nil);; => nil
(nil :k);; => NPE
({:k 4} :k);; => 4
(:k {:k 4});; => 4
;; BTW, you can also specify the "not found" case:
(:k nil :not-there);; => :not-there
See also the clojure style guide.

How to generate this expression in 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)))

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