How to find out all keys in a set of maps? - clojure

If I have a set of maps like this
(def a #{
{:a 1 :b 2}
{:a 3 :b 4}
{:b 1 :c 2}
{:d 1 :e 2}
{:d 1 :y 2}
})
: how can I find out all the keys? so doing :
(find-all-keys a)
:returns:
(:a :b :c :d :e :y)
?

Another way:
(distinct (mapcat keys a))

Almost the same way:
(set (mapcat keys a))

Something like:
user=> (into #{} (flatten (map keys a)))
#{:y :a :c :b :d :e}

Another way:
(reduce #(into %1 (keys %2)) #{} a)

Related

How to select keys in nested maps in Clojure?

Let's say I have a map (m) like this:
(def m {:a 1 :b 2 :c {:d 3 :e 4} :e { ... } ....})
I'd like to create a new map only containing :a, :b and :d from m, i.e. the result should be:
{:a 1 :b 2 :d 3}
I know that I can use select-keys to easily get :a and :b:
(select-keys m [:a :b])
But what's a good way to also get :d? I'm looking for something like this:
(select-keys* m [:a :b [:c :d]])
Does such a function exists in Clojure or what's the recommended approach?
In pure Clojure I would do it like this:
(defn select-keys* [m paths]
(into {} (map (fn [p]
[(last p) (get-in m p)]))
paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
I prefer keeping the type of a path regular, so a sequence of keys for all paths. In clojure.spec this would read as
(s/def ::nested-map (s/map-of keyword?
(s/or :num number? :map ::nested-map)))
(s/def ::path (s/coll-of keyword?))
(s/fdef select-keys*
:args (s/cat :m ::nested-map
:paths (s/coll-of ::path)))
As an alternative you can use destructing on a function, for example:
(def m {:a 1 :b 2 :c {:d 3 :e 4}})
(defn get-m
[{a :a b :b {d :d} :c}]
{:a 1 :b b :d d})
(get-m m) ; => {:a 1, :b 2, :d 3}
You can use clojure.walk.
(require '[clojure.walk :as w])
(defn nested-select-keys
[map keyseq]
(w/postwalk (fn [x]
(if (map? x)
(select-keys x keyseq)
(identity x))) map))
(nested-select-keys {:a 1 :b {:c 2 :d 3}} [:a :b :c])
; => {:a 1, :b {:c 2}}
I'm not aware of such a function being part of Clojure. You'll probably have to write it yourself. I've came up with this :
(defn select-keys* [m v]
(reduce
(fn [aggregate next]
(let [key-value (if (vector? next)
[(last next)
(get-in m next)]
[next
(get m next)])]
(apply assoc aggregate key-value)))
{}
v))
Require paths to be vectors so you can use peek (much faster than last). Reduce over the paths like this:
(defn select-keys* [m paths]
(reduce (fn [r p] (assoc r (peek p) (get-in m p))) {} paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
Of course, this assumes that all your terminal keys are unique.

Keep certain keys of a hash-map

What would be a quick way to keep only certain keys from a hash-map?
(def m {:a 1 :b 2 :c 3 :d 4})
explicit version:
((fn [{:keys [b c]}] {:b b :c c})
m)
;= {:b 2, :c 3}
select-keys:
(select-keys m [:b :c])

How to check if a map is a subset of another in clojure?

I would like to write a function that checks if a map is a subset of another.
An example of usage should be:
(map-subset? {:a 1 :b 2} {:a 1 :b 2 :c 3})
=> true
Is there a native way to do that?
By converting the maps to sets, you can use clojure.set/subset?
(clojure.set/subset? (set {:a 1 :b 2}) (set {:a 1 :b 2 :c 3}))
=> true
This would make each pair of the map an element in the set
(set {:a 1 :b 2 :c 3})
=> #{[:b 2] [:c 3] [:a 1]}
And as such, {:a 1 :b 3} would not be a subset
(clojure.set/subset? (set {:a 1 :b 3}) (set {:a 1 :b 2 :c 3}))
=> false
(defn submap?
"Checks whether m contains all entries in sub."
[^java.util.Map m ^java.util.Map sub]
(.containsAll (.entrySet m) (.entrySet sub)))
REPL demo:
(submap? {:foo 1 :bar 2 :quux 3} {:foo 1 :bar 2})
;= true
(submap? {:foo 1 :bar 2 :quux 3} {:foo 1 :bar 3})
;= false
(submap? {:foo 1 :bar 2} {:foo 1 :bar 2 :quux 3})
;= false
Another option could be:
(defn submap? [a b]
(= a (select-keys b (keys a))))
This will only check equity of the keys in the first map.
Assuming that you intend the keys and the values to match ...
Is there a native way to do that?
There is no standard function. I suggest ...
(defn map-subset? [a-map b-map]
(every? (fn [[k _ :as entry]] (= entry (find b-map k))) a-map))
A few examples:
(map-subset? {:a 1 :b 2} {:a 1 :b 2 :c 3})
=> true
(map-subset? {:d 4} {:a 1 :b 2 :c 3})
=> false
(map-subset? {:a 3} {:a 1 :b 2 :c 3})
=> false
(map-subset? {:a nil} {})
=> false
It traverses its first argument just once.
It handles nil values properly.
If neither of these are important, go with a more elegant version such as kasterma's.
Making an assumption on what you mean by subset (direct translation of that definition):
(and (every? (set (keys m1)) (keys m2)) ;; subset on keys
(every? #(= (m1 %)(m2 %)) (keys m2))) ;; on that subset all the same values
There are many possible ways to solve your question. One quick possible solution would be:
(defn contains-submap? [map-structure keys]
(every? (partial contains? map-structure) keys))
(contains-submap? {:a 1 :b 2 :c 3} (keys {:a 1 :b 2}))
true
It can be done with Sets for example. But as #kasterma pointed, it depends on your original intend.

How to reduce this collection?

I am struggling with the following problem...
Given a collection of maps
[
{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}
]
want to reduce/transform to...
{
1 {:b [1 2] :c [1 2] :d [1 2 3]}
2 {:b [1 2 3] :c 1 :d [5 6 7]}
}
group-by :a (primary key) and accumulate the distinct values for other keys.
I can do this in a brute force/imperative way, but struggling to figure out how to solve this in clojure way.
Thanks
Here is an admittedly inelegant, first-draft solution:
(defn reducing-fn [list-of-maps grouping-key]
(reduce (fn [m [k lst]]
(assoc m k (dissoc (reduce (fn [m1 m2]
(apply hash-map
(apply concat
(for [[k v] m2]
[k (conj (get m1 k #{}) v)]))))
{}
lst)
grouping-key)))
{}
(group-by #(grouping-key %) list-of-maps)))
user> (reducing-fn [{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}]
:a)
=> {2 {:c #{1}, :b #{1 2 3}, :d #{5 6 7}}, 1 {:c #{1 2}, :b #{1 2}, :d #{1 2 3}}}
Will try and figure out a more polished approach tomorrow, heading off to bed right now :)
(use 'clojure.set)
(def data
[
{:a 1 :b 1 :c 1 :d 1}
{:a 1 :b 2 :c 1 :d 2}
{:a 1 :b 2 :c 2 :d 3}
{:a 2 :b 1 :c 1 :d 5}
{:a 2 :b 1 :c 1 :d 6}
{:a 2 :b 1 :c 1 :d 7}
{:a 2 :b 2 :c 1 :d 7}
{:a 2 :b 3 :c 1 :d 7}
]
)
(defn key-join
"join of map by key , value is distinct."
[map-list]
(let [keys (keys (first map-list))]
(into {} (for [k keys] [k (vec (set (map #(% k) map-list)))]))))
(defn group-reduce [key map-list]
(let [sdata (set map-list)
group-value (project sdata [key])]
(into {}
(for [m group-value] [(key m) (key-join (map #(dissoc % key) (select #(= (key %) (key m)) sdata)))]))))
;;other version fast than group-reduce
(defn gr [key map-list]
(let [gdata (group-by key map-list)]
(into {} (for [[k m] gdata][k (dissoc (key-join m) key)]))))
user=> (group-reduce :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}
user=> (gr :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}
Another solution:
(defn pivot [new-key m]
(apply merge
(for [[a v] (group-by new-key m)]
{a (let [ks (set (flatten (map keys (map #(dissoc % new-key) v))))]
(zipmap ks (for [k ks] (set (map k v)))))})))
ETA: new-key would be the :a key here and m is your input map.
The first "for" destructures the group-by. That's where you're partitioning the data by the input "new-key." "for" generates a list - it's like Python's list comprehension. Here we're generating a list of maps, each with one key, whose value is a map. First we need to extract the relevant keys. These keys are held in the "ks" binding. We want to accumulate distinct values. While we could do this using reduce, since keywords are also functions, we can use them to extract across the collection and then use "set" to reduce down to distinct values. "zipmap" ties together our keys and their associated values. Then outside the main "for," we need to convert this list of maps into a single map whose keys are the distinct values of "a".
Another solution:
(defn transform
[key coll]
(letfn [(merge-maps
[coll]
(apply merge-with (fnil conj #{}) {} coll))
(process-key
[[k v]]
[k (dissoc (merge-maps v) key)])]
(->> coll
(group-by #(get % key))
(map process-key)
(into (empty coll)))))
Code untested, though.
EDIT: Of course it doesn't work, because of merge-with trying to be too clever.
(defn transform
[key coll]
(letfn [(local-merge-with
[f m & ms]
(reduce (fn [m [k v]] (update-in m [k] f v))
m
(for [m ms e m] e)))
(merge-maps
[coll]
(apply local-merge-with (fnil conj #{}) {} coll))
(process-key
[[k v]]
[k (dissoc (merge-maps v) key)])]
(->> coll
(group-by #(get % key))
(map process-key)
(into (empty coll)))))

How to add fields to a map in Clojure?

I have a map like this:
{:a 1 :b 20}
: and I want to make sure that certain fields are not missing from the map:
(:a :b :c :d )
: Is there a function to merge the two, something like :
(merge-missing-keys {:a 1 :b 20} (:a :b :c :d ))
: which can produce :
{:a 1 :b 20 :c nil :d nil}
?
Update:
With some pointers from the answers I found that this can be done like this:
(defn merge-missing-keys [
a-set
some-keys
]
(merge-with
#(or %1 %2)
a-set
(into {} (map (fn[x] {x nil}) some-keys))))
(merge-missing-keys {:a 1 :b 20} '(:a :b :c :d :e ))
You should use merge-with:
Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping(s)
from the latter (left-to-right) will be combined with the mapping in
the result by calling (f val-in-result val-in-latter).
So the following will merge all maps with one actual value selected from the maps or nil.
(merge-with #(or %1 %2)
{:a 1 :b 2}
{:a nil :b nil :c nil :d nil})
; -> {:d nil :c nil :b 2 :a 1}
This will probably be enough for you to build your implementation.
You can always just merge into your default array as follows:
(merge
{:a nil :b nil :c nil :d nil} ; defaults
{:a 1 :b 20}) ; current values
=> {:a 1, :b 20, :c nil, :d nil}
A riff on #mikera's answer, to make it work when you don't have the keys available as literals:
(let [keys [:a :b :c :d]]
(merge (zipmap keys (repeat nil))
{:a 1 :b 20}))