Map from list of maps - clojure

My problem is next, i have list of maps, for example:
({:id 1 :request-count 10 ..<another key-value pair>..}
{:id 2 :request-count 15 ..<another key-value pair>..}
...)
Need create map with records in which, key is value of 'id' and value is value of 'request-count', for each map from prev example, like:
{1 10
2 15
...}
I know how to do this. My question is - standard library have function for achieve this? Or maybe i can achiev this with combination few function, without 'reduce'?

Use the juxt function to generate a sequence of pairs, and then toss them into a map:
(into {} (map (juxt :id :request-count) data))
Example:
user=> (def data [{:id 1 :request-count 10 :abc 1}
#_=> {:id 2 :request-count 15 :def 2}
#_=> {:id 3 :request-count 20 :ghi 3}])
#'user/data
user=> (into {} (map (juxt :id :request-count) data))
{1 10, 2 15, 3 20}
Be aware that if there is more than one map in data with the same :id, then the last one encountered by map will be the one that survives in the output map.

I would do it like so:
(def data
[{:id 1 :request-count 10}
{:id 2 :request-count 15}] )
(defn make-id-req-map [map-seq]
(vec (for [curr-map map-seq]
(let [{:keys [id request-count]} curr-map]
{id request-count}))))
With result:
(make-id-req-map data) => [{1 10} {2 15}]
Note: while you could combine the map destructuring into the for statement, I usually like to label the intermediate values as described in Martin Fowler's refactoring "Introduce Explaining Variable".

Related

Intersecting a list of lists of maps overriding equality

I have a list of lists of maps:
(( {:id 1 :temp 1} {:id 2} )
( {:id 1 :temp 2} )
( {:id 1 :temp 3} {:id 2} ))
I want to get ids which are at intersection of these 3 sets only by :id key. So my result here will be 1
I came up with this solution but it's hurting my eyes:
(def coll '(( {:id 1 :temp 1} {:id 2} )
( {:id 1 :temp 2} )
( {:id 1 :temp 3} {:id 2} )))
(apply clojure.set/intersection
(map set (map (fn [m]
(map #(select-keys % '(:id)) m)) coll)))
returns
#{{:id 1}}
which is Ok, but any other suggestions?
If you are fine with getting #{1} (as you mention initially) instead of #{{:id 1}}, then it can be slightly improved:
(apply set/intersection (map (fn [c] (into #{} (map :id c))) coll))
(require '[clojure.set :refer [intersection]])
The select keys I guess you don't need, since you are only interested in the id. (map :id m) does the job for the inner-most map. By this you are getting rid of a function shorthand. You can use it in the next map:
(map #(map :id %) coll)
;; ((1 2) (1) (1 2))
The third map you introduce is not necessary. it can be merged in the above piece of code:
(map (comp set #(map :id %)) coll)
or:
(map #(set (map :id %)) coll)
both evaluating to: (#{1 2} #{1} #{1 2})
This is still pretty nested. Threading macros don't help here. But you can use a very powerful list comprehension macro called for:
(for [row coll]
(set (map :id row)))
This gives you the advantage of naming list items (rows) but keeping it concise at the same time.
So finally:
(apply intersection (for [row coll]
(set (map :id row))))
;; #{1}

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

Clojure. list of keywords to dict

Could you please tell me how can I make the dict form the list?
E.g. I have the list [3 4 5] and :value keyword.
I need to create the following dict:
{{:constant_keyword "constant", :value 5 } {:constant_keyword "constant", :value 4} {:constant_keyword "constant", :value 3}}?
I know how to make a constant value:
(def const-dict (take (count [my-list]) (repeat {:column "type"})))
But I do not know who to do it with a parameter like the item of an array:
(take (count [my_list]) :value) - doesn't work, I can't create list of keywords and then zipmap it to another list.
How can I do it?
(map #(assoc {:constant_keyword "constant"} :value %) [3 4 5])

GROUP BY and Aggregation on Vector of maps - Clojure

I have data that looks like this
(def a [{:firmAccount "MSFT" :Val 10 :PE 3 }
{:firmAccount "MSFT" :Val 15 :PE 4}
{:firmAccount "GOG" :Val 15 :PE 3}
{:firmAccount "YAH" :Val 8 :PE 1}])
I want to group by on :firmAccount and then SUM the :Val and :PE for each firm account and get something like
[{:firmAccount "MSFT" :Val 25 :PE 7}
{:firmAccount "GOG" :Val 15 :PE 3}
{:FirmAccount "YAH" :Val 8 :PE 1}]
It is really a trivial thing and in SQL I would not even think twice but since I am learning clojure please bear with me
Clojure.core has a built-in group-by function. The solution becomes a little ugly by the presence of both text and int vals in the maps.
(for [m (group-by :firmAccount a)]
(assoc (apply merge-with + (map #(dissoc % :firmAccount) (val m)))
:firmAccount (key m)))
And for completeness here's an alternate version that uses map:
(map (fn [[firmAccount vals]]
{:firmAccount firmAccount
:Val (reduce + (map :Val vals))
:PE (reduce + (map :PE vals))})
(group-by :firmAccount a))
Try creating a new map array or map of maps with the same structure. You can write a function to add elements to this new map that sums that fields if the :firm-account exists. Maybe a map like this?
(def a {"MSFT" {:Val 25 :PE 7 }
"GOG" {:Val 15 :PE 3}
"YAH" {:Val 8 :PE 1}})
With a personalized add function like:
(add-to-map [element map]
(if (contains? (find-name element))
{map (add-fields element (find (find-name element)))}
{map element}))
It's a matter of style, but I find using thread-last macro (->>) is easier to read.
(->> a
(group-by :firmAccount)
(map (fn [[firmAccount vals]]
{:firmAccount firmAccount
:Val (reduce + (map :Val vals))
:PE (reduce + (map :PE vals))})

How can I update a vector item in Clojure?

Given:
(def my-vec [{:id 0 :a "foo" :b "bar"} {:id 1 :a "baz" :b "spam"}
{:id 2 :a "qux" :b "fred"}])
How can I idiomatically update * the item in my-vec with :id=1 to have values :a="baz2" and :b="spam2"?
*: I recognize that I wouldn't actually be updating my-vec, but really returning a new vector that is identical to my-vec except for the replacement values.
Do you know ahead of time that the map with id == 1 is the second map in your vector? If so:
user> (-> my-vec
(assoc-in [1 :a] "baz2")
(assoc-in [1 :b] "spam2"))
[{:id 0, :a "foo", :b "bar"} {:id 1, :a "baz2", :b "spam2"} {:id 2, :a "qux", :b "fred"}]
If you need to access your data by id a lot, another idea is to replace your vector of hash-maps with a hash-map of hash-maps keyed on :id. Then you can more easily assoc-in no matter the order of things.
user> (def new-my-vec (zipmap (map :id my-vec) my-vec))
#'user/new-my-vec
user> new-my-vec
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz", :b "spam"}, 0 {:id 0, :a "foo", :b "bar"}}
user> (-> new-my-vec
(assoc-in [1 :a] "baz2")
(assoc-in [1 :b] "spam2"))
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz2", :b "spam2"}, 0 {:id 0, :a "foo", :b "bar"}}
map a function over the vector of maps that either creates a modified map if the key matches or uses the original if the keys don't match then turn the result back into a vector
(vec (map #(if (= (:id %) 1)
(assoc % :a "baz2" :b "spam2")
%)))
It is possible to do this more succinctly though this one really shows where the structural sharing occurs.
Might want to take a look at array-map which creates a map backed by an array and keyed by the index instead of using :id?