Combining Two clojure.walk/postwalk Calls Into One - clojure

I have the following data:
28 (def example {"1ce9b863-5681-4660-85e7-fbd0cc184aed"
29 {"58825b50-23bc-4204-8f8d-c9a9d3ac8beb" {},
30 "4b1763f9-8380-4507-9a8f-5c86878e49a9" {},
31 "160f34ac-68b9-4c8e-930b-1ab6df895df4" {}},
32 "6378daf6-3b7f-4cf4-8156-a50cf5f7b6ef"
33 {"669fe949-057f-43c0-af7b-ff39594a183d" {},
34 "73d2a203-e3c1-4d2f-aaf8-a9f2e870792b" {},
35 "8c9c57a0-d20d-4474-9afb-c9d17df83a91" {},
36 "94bf72cb-01cd-4430-b669-b2e954b5639b"
37 {"ba96a425-a3f0-4ce5-8c19-6ea9add14013" {},
38 "1ceff8fe-a0a8-46ad-81a8-d13fb837aaf6" {}}}})
39
40 (def titles (list {:id "58825b50-23bc-4204-8f8d-c9a9d3ac8beb", :title "Low"}
41 {:id "4b1763f9-8380-4507-9a8f-5c86878e49a9", :title "Medium"}
42 {:id "160f34ac-68b9-4c8e-930b-1ab6df895df4", :title "High"}
43 {:id "1ce9b863-5681-4660-85e7-fbd0cc184aed", :title "Priority"}
44 {:id "1ceff8fe-a0a8-46ad-81a8-d13fb837aaf6", :title "Drafting"}
45 {:id "ba96a425-a3f0-4ce5-8c19-6ea9add14013", :title "Brainstorm"}
46 {:id "94bf72cb-01cd-4430-b669-b2e954b5639b", :title "Planning"}
47 {:id "8c9c57a0-d20d-4474-9afb-c9d17df83a91", :title "Testing"}
48 {:id "73d2a203-e3c1-4d2f-aaf8-a9f2e870792b", :title "Implementation"}
49 {:id "669fe949-057f-43c0-af7b-ff39594a183d", :title "Completed"}
50 {:id "6378daf6-3b7f-4cf4-8156-a50cf5f7b6ef", :title "Status"}))
I am attempting to turn this data into a nested list structure using sablono/hiccup style syntax. Currently I have the following working solution:
52 (defn id->title [x]
53 (let [tuples (map (fn [x] [(:id x) (:title x)]) titles)]
54 (first (for [[k t] tuples :when (= k x)] t))))
57 (->> example
58 (clojure.walk/postwalk
59 (fn [x] (if-not (string? x)
60 (vec x) x)))
61 (clojure.walk/postwalk
62 (fn [x] (if (vector? x)
63 (if (vector? (first x))
64 (vec (cons :ul x))
65 (vec (cons :li x)))
66 (id->title x)))))
This results in:
[:ul
[:li "Priority"
[:ul
[:li "Low" [:li]]
[:li "Medium" [:li]]
[:li "High" [:li]]]]
[:li "Status"
[:ul
[:li "Completed" [:li]]
[:li "Implementation" [:li]]
[:li "Testing" [:li]]
[:li "Planning"
[:ul
[:li "Brainstorm" [:li]]
[:li "Drafting" [:li]]]]]]]
How can I simplify this to use one walk? I am also considering replacing titles with a map for an efficient lookup (I am pulling all of this data from a Neo4j).

Given the data you provided, you could try something like the following:
(defn id->title [x]
(->> titles (filter #(= (:id %) x)) first :title))
(defn format-data [structure]
(clojure.walk/postwalk
(fn [x]
(if (map? x)
(into [:ul] (map (fn [[k v]]
(if (= v [:ul])
[:li (id->title k)]
[:li (id->title k) v]))
(seq x)))
x))
structure))
During the postwalk, this will turn each map into a vector representing an unordered list (even the empty maps) and each key-value pair into a vector representing a list item. The (if (= v [:ul]) ...) ensures that the empty unordered lists are removed from the final structure.
Running (pprint (format-data example)) gives the following results:
[:ul
[:li "Priority" [:ul [:li "Low"] [:li "Medium"] [:li "High"]]]
[:li
"Status"
[:ul
[:li "Completed"]
[:li "Implementation"]
[:li "Testing"]
[:li "Planning" [:ul [:li "Brainstorm"] [:li "Drafting"]]]]]]

Related

Empty children and Clojure zippers

Why the last expression retruns
{:a :foo, :args [{:id :XX}], :id :XX}
instead of:
{:a :foo, :args [], :id :XX}
(require '[clojure.zip :as zip])
(defn my-zipper [tree]
(zip/zipper
(fn branch? [node]
(:args node))
(fn children [node]
(:args node))
(fn make-node [node children]
(assoc node :args (vec children)))
tree))
(def z (my-zipper {:a :foo :args []}))
(loop [loc z]
(if (zip/end? loc)
(zip/node loc)
(recur
(zip/next
(zip/edit loc #(assoc % :id :XX))))))
It looks like the problem is associated with the fact that traversing with zip/next reveals there are 2 nodes :
(zip/node (zip/next z)) ; => nil
(zip/node (zip/next (zip/next z))) ; => {:a :foo :args []}
Why is that? There is a single node with empty children so there should be only one node, correct?
After looking at the code of clojure.zip/vector-zip I conclude that lack of node's children should be communicated with nil. The empty sequence doesn't work.
So the children function should really be:
(fn children [node]
(seq (:args node)))

How to group-by a collection that is already grouped by in Clojure?

I have a collection of maps
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
I want to group this first by value, followed by type.
My output should look like:
{
:orange [{
:value 3,
:id [9345, 145]
}, {
:value 2,
:id [2935]
}],
:apple [{
:value 6,
:id [2745, 2345]
}]
}
How would I do this in Clojure? Appreciate your answers.
Thanks!
Edit:
Here is what I had so far:
(defn by-type-key [data]
(group-by #(get % "type") data))
(reduce-kv
(fn [m k v] (assoc m k (reduce-kv
(fn [sm sk sv] (assoc sm sk (into [] (map #(:id %) sv))))
{}
(group-by :value (map #(dissoc % :type) v)))))
{}
(by-type-key a))
Output:
=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345], 3 [125]}}
I just couldnt figure out how to proceed next...
Your requirements are a bit inconsistent (or rather irregular) - you use :type values as keywords in the result, but the rest of the keywords are carried through. Maybe that's what you must do to satisfy some external formats - otherwise you need to either use the same approach as with :type through, or add a new keyword to the result, like :group or :rows and keep the original keywords intact. I will assume the former approach for the moment (but see below, I will get to the shape as you want it,) so the final shape of data is like
{:orange
{:3 [9345 145],
:2 [2945]},
:apple
{:6 [2745 2345]}
}
There is more than one way of getting there, here's the gist of one:
(group-by (juxt :type :value) a)
The result:
{["orange" 3] [{:id 9345, :value 3, :type "orange"} {:id 145, :value 3, :type "orange"}],
["orange" 2] [{:id 2945, :value 2, :type "orange"}],
["apple" 6] [{:id 2745, :value 6, :type "apple"} {:id 2345, :value 6, :type "apple"}]}
Now all rows in your collection are grouped by the keys you need. From this, you can go and get the shape you want, say to get to the shape above you can do
(reduce
(fn [m [k v]]
(let [ks (map (comp keyword str) k)]
(assoc-in m ks
(map :id v))))
{}
(group-by (juxt :type :value) a))
The basic idea is to get the rows grouped by the key sequence (and that's what group-by and juxt do,) and then combine reduce and assoc-in or update-in to beat the result into place.
To get exactly the shape you described:
(reduce
(fn [m [k v]]
(let [type (keyword (first k))
value (second k)
ids (map :id v)]
(update-in m [type]
#(conj % {:value value :id ids}))))
{}
(group-by (juxt :type :value) a))
It's a bit noisy, and it might be harder to see the forest for the trees - that's why I simplified the shape, to highlight the main idea. The more regular your shapes are, the shorter and more regular your functions become - so if you have control over it, try to make it simpler for you.
I would do the transform in two stages (using reduce):
the first to collect the values
the second for formating
The following code solves your problem:
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
(defn standardise [m]
(->> m
;; first stage
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{})
;; second stage
(reduce-kv (fn [out k v]
(assoc out (keyword k)
(reduce-kv (fn [out value id]
(conj out {:value value
:id id}))
[]
v)))
{})))
(standardise a)
;; => {:orange [{:value 3, :id [9345 145]}
;; {:value 2, :id [2945]}],
;; :apple [{:value 6, :id [2745 2345]}]}
the output of the first stage is:
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{}
a)
;;=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345]}}
You may wish to use the built-in function group-by. See http://clojuredocs.org/clojure.core/group-by

Filtering a map based on expected keys

In my Clojure webapp I have various model namespaces with functions that take a map as an agrument and somehow insert that map into a database. I would like to be able take out only the desired keys from the map before I do the insert.
A basic example of this is:
(let [msg-keys [:title :body]
msg {:title "Hello" :body "This is an example" :somekey "asdf" :someotherkey "asdf"}]
(select-keys msg msg-keys))
;; => {:title "Hello" :body "This is an example"}
select-keys is not an option when the map is somewhat complex and I would like to select a specific set of nested keys:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(some-select-key-fn person [:name [:first] :something [:a :b]]))
;; => {:name {:first "john"} :something {:a "a" :b "b"}}
Is there a way to do this with the core functions? Is there a way do this purely with destructuring?
This topic was discussed in the Clojure Google Group along with a few solutions.
Destructuring is probably the closest to a "core" capability, and may be a fine solution if your problem is rather static and the map has all of the expected keys (thus avoiding nil). It could look like:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}
{{:keys [first]} :name {:keys [a b]} :something} person]
{:name {:first first} :something {:a a :b b}})
;; => {:name {:first "john"}, :something {:a "a", :b "b"}}
Below is a survey of the solutions in the Clojure Google Group thread, applied to your sample map. They each have a different take on how to specify the nested keys to be selected.
Here is Christophe Grand's solution:
(defprotocol Selector
(-select [s m]))
(defn select [m selectors-coll]
(reduce conj {} (map #(-select % m) selectors-coll)))
(extend-protocol Selector
clojure.lang.Keyword
(-select [k m]
(find m k))
clojure.lang.APersistentMap
(-select [sm m]
(into {}
(for [[k s] sm]
[k (select (get m k) s)]))))
Using it requires a slightly modified syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select person [{:name [:first] :something [:a :b]}]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Moritz Ulrich's solution (he cautions that it doesn't work on maps with seqs as keys):
(defn select-in [m keyseq]
(loop [acc {} [k & ks] (seq keyseq)]
(if k
(recur
(if (sequential? k)
(let [[k ks] k]
(assoc acc k
(select-in (get m k) ks)))
(assoc acc k (get m k)))
ks)
acc)))
Using it requires another slightly modified syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select-in person [[:name [:first]] [:something [:a :b]]]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Jay Fields's solution:
(defn select-nested-keys [m top-level-keys & {:as pairs}]
(reduce #(update-in %1 (first %2) select-keys (last %2)) (select-keys m top-level-keys) pairs))
It uses a different syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select-nested-keys person [:name :something] [:name] [:first] [:something] [:a :b]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Baishampayan Ghose's solution:
(defprotocol ^:private IExpandable
(^:private expand [this]))
(extend-protocol IExpandable
clojure.lang.Keyword
(expand [k] {k ::all})
clojure.lang.IPersistentVector
(expand [v] (if (empty? v)
{}
(apply merge (map expand v))))
clojure.lang.IPersistentMap
(expand [m]
(assert (= (count (keys m)) 1) "Number of keys in a selector map can't be more than 1.")
(let [[k v] (-> m first ((juxt key val)))]
{k (expand v)}))
nil
(expand [_] {}))
(defn ^:private extract* [m selectors expand?]
(let [sels (if expand? (expand selectors) selectors)]
(reduce-kv (fn [res k v]
(if (= v ::all)
(assoc res k (m k))
(assoc res k (extract* (m k) v false))))
{} sels)))
(defn extract
"Like select-keys, but can select nested keys.
Examples -
(extract [{:b {:c [:d]}} :g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:g 42, :b {:c {:d 1}}}
(extract [:g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:g 42}
(extract [{:b [:c]} :xxx] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:b {:c {:d 1, :e 2}}, :xxx 11}
Also see - exclude"
[selectors m]
(extract* m selectors true))
It uses another syntax (and the parameters are reversed):
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(extract [{:name [:first]} {:something [:a :b]}] person))
;; => {:name {:first "john"}, :something {:a "a", :b "b"}}
Your best bet is probably to use select keys on each nested portion of the structure.
(-> person
(select-keys [:name :something])
(update-in [:name] select-keys [:first])
(update-in [:something] select-keys [:a :b]))
You could of course use a generalized version of the above to implement the syntax you suggest in a function (with a reduce rather than a -> form, most likely, and recursive calls for each nested selection of keys). Destructuring would not help much, it makes binding nested data convenient, but isn't really that useful for constructing values.
Here is how I would do it with reduce and recursion:
(defn simplify
[m skel]
(if-let [kvs (not-empty (partition 2 skel))]
(reduce (fn [m [k nested]]
(if nested
(update-in m [k] simplify nested)
m))
(select-keys m (map first kvs))
kvs)
m))
note that your proposed argument format is inconvenient, so I changed it slightly
user=> (simplify {:name {:first "john" :john "smith"}
:age 40
:weight 155
:something {:a "a" :b "b" :c "c" :d "d"}}
[:name [:first nil] :something [:a nil :b nil]])
{:something {:b "b", :a "a"}, :name {:first "john"}}
the syntax you propose will require a more complex implementation

applying function in a map to each value in a map based on its key

Suppose I have a map:
{:name "foo"
:age "bar"}
And another one
{:name (fn [val] (println val))
:age (fn [val] (= val "bar"))}
I want to apply function keyed by :name on second map to the first map, which also keyed by :name and the function keyed by :age to the first map which keyed by :age. How to do this the clojure way?
You can use merge-with
(def m1 {:name "foo"
:age "bar"})
(def m2 {:name (fn [val] (println val))
:age (fn [val] (= val "bar"))})
user=> (merge-with #(%1 %2) m2 m1)
foo
{:name nil, :age true}
map over one map and get corresponding function from the other one.
(def m1 {:name "foo"
:age "bar"})
(def m2 {:name (fn [val] (println val))
:age (fn [val] (= val "bar"))})
(map (fn [[k v]]
((get m2 k) v))
m1)
Each iteration over the map passes a vector to the function, in your sample:
[:name "foo"]
[:age "bar"]
So destructuring the function parameter into [[k v]] gives you each key/value separately.
(def data { :name "don knotts"
:dob "1/1/1940"
:cob "Valdosta" })
(def fxns {:name identity :dob identity :cob clojure.string/reverse})
(defn bmap [data fxn]
(apply merge (for [[k1 d] data [k2 f] fxn :when (= k1 k2)]
{k1 (f d)})))
;=user>{:cob "atsodlaV", :dob "1/1/1940", :name "don knotts"}
I like this, if you need more resilience:
(defn fmm [m fm]
(let [f (fn [k] ((get fm k identity) (k m)))
ks (keys m)]
(zipmap ks (map f ks))))

Append to an attribute in Enlive

Is it possible to append a value to an attribute using enlive?
example: I have this
edit
and would like this
edit
I am currently doing this:
(html/defsnippet foo "views/foo.html" [:#main]
[ctxt]
[:a] (html/set-attr :href (str "/item/edit/" (ctxt :id))))
But I would prefer not to embed the URL into my code, by just appending the id to the existing URL
(html/defsnippet foo "views/foo.html" [:#main]
[ctxt]
[:a#href] (html/append (ctxt :id)))
#ddk answer is spot on but you may prefer a more generic way to solve the problem
(defn update-attr [attr f & args]
(fn [node]
(apply update-in node [:attrs attr] f args))))
and then
(update-attr :href str "123")
You could always write your own append-attr in the same vein as set-attr. Here is my attempt
(defn append-attr
[& kvs]
(fn [node]
(let [in-map (apply array-map kvs)
old-attrs (:attrs node {})
new-attrs (into {} (for [[k v] old-attrs]
[k (str v (get in-map k))]))]
(assoc node :attrs new-attrs))))
Which gives the following, when appending "/bar" to href, on enlive's representation of A link
((append-attr :href "/bar")
{:tag :a, :attrs {:href "/foo"}, :content "A link"})
;=> {:tag :a, :attrs {:href "/foo/bar"}, :content "A link"}