Extract set of values from collection of maps by specific keys - clojure

I'm trying to extract set of values from collection of maps by specific keys. For example,
Input:
[
{:k1 "v1" :k2 "v2" :k3 "v3"}
{:k1 "v4" :k2 "v2"}
]
Assuming that getting set of values by :k1, :k2
Desired output:
#{"v1" "v2" "v4"}
So my solution is
(->> [{:k1 "v1" :k2 "v2" :k3 "v3"}
{:k1 "v4" :k2 "v2"}]
(map #(-> (select-keys % [:k1 :k2]) (vals) (set)))
(apply clojure.set/union))
But I wanna know better ways. What do you think?

Using into and a transducer, we can do the following:
(def input [{:k1 "v1" :k2 "v2" :k3 "v3"} {:k1 "v4" :k2 "v2"}])
(into #{} (mapcat (juxt :k1 :k2)) input) => #{"v4" "v1" "v2"}
Alternatively, we can use the composition of cat and map in lieu of mapcat:
(into #{} (comp (map (juxt :k1 :k2)) cat) input) => #{"v4" "v1" "v2"}

Related

Clojure - Filter keys from a map [duplicate]

This question already has answers here:
How to remove multiple keys from a map?
(4 answers)
Closed 2 years ago.
Let's assume I have a hashmap and I want to filter out entry by keys provided in a given vector. For example assuming I have
1. map: {:k1 "v1" :k2 "v2" :k3 "v3"}
2. list: [:k2 :k4]
and I want to be left with k1, k3
My current solution is:
(defn rr
"remove key that are in set from the map"
[m1 s]
(loop [mm m1 ss s]
(if (first ss)
(recur (dissoc mm (first ss)) (rest ss))
mm)))
Wonder do you prettier solution?
(apply dissoc {:k1 "v1" :k2 "v2" :k3 "v3"} [:k2 :k4])
Since dissoc can take multiple keys to remove, it can operate on keys collection with apply, or else you can use reduce the same way:
(reduce dissoc {:k1 "v1" :k2 "v2" :k3 "v3"} [:k2 :k4])
so your function rr could be:
(def dissoc-keyset (partial apply dissoc))

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

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 traverse / walk an arbitrary embedded structure

I want a mechanism to traverse an arbitrarily nested data structure. Then apply a fn on every node, and then check if the fn returned true at each point.
Its easy to do this with a flat structure -
(walk (complement string?) #(every? true? %) [ 1 2 3 4])
However walk doesnt work with a nested one -
(walk (complement string?) #(every? true? %) [ 1 2 3 [ "a" ]])
Using only flatten also wont work, as I will have a map as one of the forms, and I want fn applied to each value in the map too. This is the structure I will have -
[ ["2012" [{:a 2} {:b 3}]] ["2013" [{:a 2} {:b 3}]] ]
I can easily write a fn to only traverse the above and apply the fn to each val. However is there a way to write a generic mechanism for traversing?
tree-seq might be what you want
(every? (complement string?)
(remove coll?
(tree-seq coll? #(if (map? %)
(vals %)
%)
[["2012" [{:a 2} {:b 3}]] ["2013" [{:a 2} {:b 3}]]])))
;; false
(every? (complement string?)
(remove coll?
(tree-seq coll? #(if (map? %)
(vals %)
%)
[[2012 [{:a 2} {:b 3}]] [2013 [{:a 2} {:b 3}]]])))
;; true

Pulling values from complex lists in Clojure

I'm trying to pull the values out of a complicated list structure.
Given something like this:
[{:a "whatever" :b [:c "foo"]} :e {:f "boo"} :g {:h [:i 62281]}]
I'd like to get:
["whatever" "foo" "boo" 62281]
So far I've only gotten to this point:
((62281) nil (boo) nil whatever foo)
Here's the code:
(defn get-values [params]
(apply conj
(map (fn [part]
(if (not (keyword? part))
(map (fn [v]
(if (vector? v)
(last v)
v))
(vals part))))
params)))
I can't seem to get rid of the nil's
I can't figure out why the values after a certain point are in lists.
I figure there's got to be a better way to do this.
Fix the data structure and everything will fall in place. As of now your data structure isn't consistent at all and that will make any function that touch this data way more complicated and error prone. You can model this data as a map:
(def data {:a "whatever"
:b nil
:c "foo"
:e nil
:f "boo"
:g nil
:h nil
:i 62281})
And then to get the desired result:
(->> (vals data)
(filter (comp not nil?))
(into []))
But for some strange reason you still want to parse the data structure you provided then:
(defn get-values [data]
(->> (map #(if (map? %) (into [] %) %) data)
flatten
(filter #(or (string? %) (number? %)))
(into [])))