How to add fields to a map in Clojure? - 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}))

Related

Clojure - storing maps into a list of maps on keys

Say I have two maps in clojure.
(def map1 {:a 1 :b 1 :c nil :d 1})
(def map2 {:a 1 :b 2 :c 3 :d nil})
(def listofmaps '({:a 1 :b 1 :c nil :d 1} {:a 2 :b 2 :c 2 :d nil}))
If :a value matches with any map in listofmaps and map1, then if map1 :d is not null, put :d value from map1 into the matching map in listofmaps.
Like, first we compare map1 and listofmaps - now if map1 (:a 1) matches with any maps in listofmaps (:a 1), if map1 (:d not null) replace (matching map in listofmaps with map1 :d value) and if map1 (:c not null) replace (matching map in listofmaps with map1 :c value)
(def map1 {:a 1 :b 1 :c nil :d 1})
(def listofmaps '({:a 1 :b 1 :c nil :d 1} {:a 2 :b 2 :c 2 :d nil}))
Then map2 (:a 1) matches with a map in listofmaps (:a 1) and :
(def map2 {:a 1 :b 2 :c 3 :d nil})
(def listofmaps '({:a 1 :b 1 :c nil :d 1} {:a 2 :b 2 :c 2 :d nil}))
if map2 (:d not null) replace (matching map in listofmaps with map2 :d value) and if map2 (:c not null) replace (matching map in listofmaps with map2 :c value)
output=> '({:a 1 :b 1 :c 3 :d 1} {:a 2 :b 2 :c 2 :d nil})
it's not clear what is meant by in list of maps and map2, here is a reasonably common pattern of adding in missing values in priority order.
(let [map1 {:a 1 :b 1 :c nil :d 1}
map2 {:a 1 :b 2 :c 3 :d nil}
list-of-maps [{:a 1 :b 1 :c nil :d 1} {:a 2 :b 2 :c 2 :d nil}]
or-fn (fn [a b] (or a b))]
(->>
list-of-maps
(map #(merge-with or-fn % map1))
(map #(merge-with or-fn % map2))))
({:a 1, :b 1, :c 3, :d 1} {:a 2, :b 2, :c 2, :d 1})
I understood your question to be
If the value of the :a key in the new map matches the value of the :a key in any map in listofmaps, then, for each such matched map, replace the values of the keys :c and :d in that matched map in listofmaps by a new value only if the corresponding new value is not null.
Assuming that, here is an answer.
If I did not understand the question correctly, please clarify and show your desired output.
user> (def map1 {:a 1 :b 1 :c nil :d 1})
#'user/map1
user> (def map2 {:a 1 :b 2 :c 3 :d nil})
#'user/map2
user> (def listofmaps [{:a 1 :b 1 :c nil :d 1} {:a 2 :b 2 :c 2 :d nil}])
#'user/listofmaps
user> (defn mapper [m ms]
(mapv (fn [elem]
(if (= (:a elem) (:a m))
(merge-with #(or %1 %2) (select-keys m [:c :d]) elem)
elem))
maps))
#'user/mapper
user> (mapper map1 listofmaps)
[{:c nil, :d 1, :a 1, :b 1} {:a 2, :b 2, :c 2, :d nil}]
user> (mapper map2 listofmaps)
[{:c 3, :d 1, :a 1, :b 1} {:a 2, :b 2, :c 2, :d nil}]
user>

Using specter to transform values that match a key

I'm sorry if this has been answered elsewhere, but I can't seem to find an example that matches the pattern of what I'm looking for. I also may not yet understand recursive specter paths fully.
If I have the data (explicitly with the nested vector):
{:a "1" :b "2" :c [ {:a "3" :b "4"} {:a "5" :b "6"} ]}
And I'd like to apply the keyword function to all values with the key :a to result in:
{:a :1 :b "2" :c [ {:a :3 :b "4"} {:a :5 :b "6"} ]}
Finally, I'd like it to be recursive to an arbitrary depth, and handle the vector case as well.
I've read https://github.com/nathanmarz/specter/wiki/Using-Specter-Recursively , but I must be missing something critical.
Thanks to anyone pointing me in the right direction!
(use '[com.rpl.specter])
(let [input {:a "1" :b "2" :c [{:a "3" :b "4"} {:a "5" :b "6"}]}
desired-output {:a :1 :b "2" :c [{:a :3 :b "4"} {:a :5 :b "6"}]}
FIND-KEYS (recursive-path [] p (cond-path map? (continue-then-stay [MAP-VALS p])
vector? [ALL p]
STAY))]
(clojure.test/is
(= (transform [FIND-KEYS (must :a)] keyword input)
desired-output)))
Not a Specter solution, but it is easily done via clojure.walk/postwalk:
(ns demo.core
(:require
[clojure.walk :as walk] ))
(def data {:a "1" :b "2" :c [{:a "3" :b "4"} {:a #{7 8 9} :b "6"}]})
(def desired {:a :1 :b "2" :c [{:a :3 :b "4"} {:a #{7 8 9} :b "6"}]})
(defn transform
[form]
(if (map-entry? form)
(let [[key val] form]
(if (and
(= :a key)
(string? val))
[key (keyword val)] ; can return either a 2-vector
{key val})) ; or a map here
form))
(walk/postwalk transform data) =>
{:a :1, :b "2", :c [{:a :3, :b "4"} {:a #{7 9 8}, :b "6"}]}
I even put in a non-string for one of the :a values to make it trickier.

merge two lists by some map key

I'd like to merge two lists by some map key as follow:
(def list1 '({:a 2 :b 2} {:a 1 :b 1}))
(def list2 '({:a 1 :c 1} {:a 2 :c 2}))
As result I'd like something like, using sort by :a for example:
'({:a 1 :b 1 :c 1} {:a 2 :b 2 :c 2})
Any ideas?
You can use join and sort-by:
(:require '[clojure.set :as s])
(sort-by :a (s/join list1 list2 {:a :a}))
Does this do it?
(def list1 '({:a 1 :b 1} {:a 2 :b 2}))
(def list2 '({:a 1 :c 1} {:a 2 :c 2}))
(println
(map merge list1 list2)
)
;=> ({:a 1, :b 1, :c 1} {:a 2, :b 2, :c 2})
UPDATE
(def list1 [ {:a 1 :b 1} {:a 2 :b 2} ] )
(def list2 [ {:a 2 :c 2} {:a 1 :c 1} ] )
(defn sort-merge [lista listb]
(map merge (sort-by :a lista) (sort-by :a listb)))
(println
(sort-merge list1 list2))
;=> ({:a 1, :b 1, :c 1} {:a 2, :b 2, :c 2})
another way is to use list comprehension:
user> (for [x list1
y list2
:when (= (:a x) (:a y))]
(merge x y))
({:a 2, :b 2, :c 2} {:a 1, :b 1, :c 1})

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 find out all keys in a set of maps?

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)