How to write a dissoc-in command for clojure? - clojure

I'm looking to write a function that is similar to assoc-in but removes keys instead of adding it:
(dissoc-in {:a {:b 0}} [:a :b])
;;=> {:a {}}
I got up to here:
(def m {:a {:b {:c 1}}})
(assoc m :a (assoc (:a m) :b (dissoc (:b (:a m)) :c)))
;;=> {:a {:b {}}}
but the whole nested thing is messing with my head

I write this using update-in:
(update-in {:a {:b 0 :c 1}} [:a] dissoc :b)
=>
{:a {:c 1}}

How about:
(defn dissoc-in
"Dissociates an entry from a nested associative structure returning a new
nested structure. keys is a sequence of keys. Any empty maps that result
will not be present in the new structure."
[m [k & ks :as keys]]
(if ks
(if-let [nextmap (get m k)]
(let [newmap (dissoc-in nextmap ks)]
(if (seq newmap)
(assoc m k newmap)
(dissoc m k)))
m)
(dissoc m k)))
Example:
(dissoc-in {:a {:b 0 :c 1}} [:a :b])
Result:
{:a {:c 1}}
dissoc-in was once part of clojure.contrib.core, and is now part of core.incubator.
If you want to keep empty maps, you can alter the code slightly:
(defn dissoc-in
[m [k & ks :as keys]]
(if ks
(if-let [nextmap (get m k)]
(let [newmap (dissoc-in nextmap ks)]
(assoc m k newmap))
m)
(dissoc m k)))
Example:
(dissoc-in {:a {:b {:c 0}}} [:a :b])
Result:
{:a {}}

Here is a general solution based on update-in:
(defn dissoc-in [m p]
(if (get-in m p)
(update-in m
(take (dec (count p)) p)
dissoc (last p))
m))

Being inspired by Dominic's code. I wrote a more succinct version
(defn dissoc-in
[m [k & ks]]
(if-not ks
(dissoc m k)
(assoc m k (dissoc-in (m k) ks))))
(dissoc-in {:a {:b {:c 1}}} [:a :b :c]) ;; => {:a {:b {}}}
Another version dissoc-in2 recursively removes empty maps
(defn dissoc-in2
[m [k & ks]]
(if-not ks
(dissoc m k)
(let [nm (dissoc-in2 (m k) ks)]
(cond (empty? nm) (dissoc m k)
:else (assoc m k nm)))))
(ut/dissoc-in {:a {:b {:c 3}}} [:a :b :c])
;;; => {:a {:b {}}}
(ut/dissoc-in2 {:a {:b {:c 3}}} [:a :b :c])
;;=> {}

No need to write one, clojure.core.incubator already has a dissoc-in:
=> (dissoc-in {:children [{:name "Billy" :age 5}]} [:children 0 :age])
{:children [{:name "Billy"}]}

I'd recommend using dissoc-in from the medley library.
Here is the code as of version 0.7.0:
(defn dissoc-in
"Dissociate a value in a nested assocative structure, identified by a sequence
of keys. Any collections left empty by the operation will be dissociated from
their containing structures."
[m ks]
(if-let [[k & ks] (seq ks)]
(if (seq ks)
(let [v (dissoc-in (get m k) ks)]
(if (empty? v)
(dissoc m k)
(assoc m k v)))
(dissoc m k))
m))
Here's a link to the source code of dissoc-in in medley master.

(defn dissoc-in [m ks]
(let [parent-path (butlast ks)
leaf-key (last ks)]
(if (= (count ks) 1)
(dissoc m leaf-key)
(update-in m parent-path dissoc leaf-key))))

Related

Clojure - Count occurences of nested key in nested map?

I have a nested map like so:
{:A {:B {:A {:B {:A {:B 0}}}}}}
I want to count the occurrences of the pair [:A :B] so that in the case above, the result is 3.
My initial idea was to use clojure.walk/postwalk to traverse the map and increment a counter.
Is there a more optimal way of achieving this?
tree-seq works out nice in this case:
(defn count-ab [data]
(->> data
(tree-seq coll? seq)
(keep #(get-in % [:A :B]))
count))
user> (def data {:A {:B {:A {:B {:A {:B 0}}}}}})
#'user/data
user> (count-ab data)
3
user> (def data-1 {:A {:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-1
user> (count-ab data-1)
3
user> (def data-2 {:A {:X {:A {:B 100}}
:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-2
user> (count-ab data-2)
4
Because it's nested map, my pragmatic idea is to traverse recursively and count:
(defn do-count [m]
(loop [m m
counter 0]
(if-let [m* (get-in m [:A :B])]
(recur m* (inc counter))
counter)))
(do-count {:A {:B {:A {:B {:A {:B 0}}}}}}) ;==> 3
I would suggest writing a function to turn the deeply nested map into a flat map keyed by paths
(defn flatten-map
"unwind deeply nested map into map of paths and vals"
([m] (into {} (flatten-map [] m)))
([path m]
(if (map? m)
(mapcat (fn [[k v]]
(flatten-map (conj path k) v))
m)
[[path m]])))
you can use this to count the adjacent [:a :b] keys like this
(->> {:A {:B {:A {:B {:A {:B 0}}}}}}
flatten-map
keys
(mapcat #(partition 2 1 %))
(filter #(= [:A :B] %))
count)

compare Clojure maps with bigdecimal and integers values

In my REPL :
(== 1 1M)
;;=>true
(= {:a 1 :b 2} {:a 1 :b 2})
;;=>true
(= {:a 1 :b 2} {:a 1 :b 3})
;;=>false
(= {:a 1M :b 2M} {:a 1 :b 2})
;;=>false
How can I compare these maps so that the result of the last example would return true ?
You want to make sure that:
1) The maps have the same keys.
2) every value for a key has an equivalent (==) value in both maps.
Here's my first thought, I'm sure it could be made more succint:
(defn number-equivalent
[m1 m2]
(let [k1 (keys m1)]
(and (= k1 (keys m2))
(every? true?
(for [k k1]
(== (m1 k)
(m2 k)))))))
Here's a solution that will work for multiple maps
(defn number-equivalent [& ms]
(->> (apply merge-with == ms)
(every? (comp true? val))))
(defn map== [a b]
(and (= (count a) (count b))
(reduce-kv (fn [_ k va]
(or (and (number? va)
(let [vb (get b k)]
(and (number? vb)
(== va vb))))
(reduced false)))
true a)))
Compare corresponding values with ==
(defn compare-nums
[m1 m2]
(every? (fn [[k v]] (== (get m1 k) v)) m2))
Note that both maps need to have the same keys. You might want to add a precondition like
{:pre [(= (.keySet m1) (.keySet m2))]} ; ensure both maps have the same keys

Find Value of Specific Key in Nested Map

In Clojure, how can I find the value of a key that may be deep in a nested map structure? For example:
(def m {:a {:b "b"
:c "c"
:d {:e "e"
:f "f"}}})
(find-nested m :f)
=> "f"
Clojure offers tree-seq to do a depth-first traversal of any value. This will simplify the logic needed to find your nested key:
(defn find-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(some k)))
(find-nested {:a {:b {:c 1}, :d 2}} :c)
;; => 1
Also, finding all matches becomes a matter of replacing some with keep:
(defn find-all-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(keep k)))
(find-all-nested {:a {:b {:c 1}, :c 2}} :c)
;; => [2 1]
Note that maps with nil values might require some special treatment.
Update: If you look at the code above, you can see that k can actually be a function which offers a lot more possibilities:
to find a string key:
(find-nested m #(get % "k"))
to find multiple keys:
(find-nested m #(some % [:a :b]))
to find only positive values in maps of integers:
(find-nested m #(when (some-> % :k pos?) (:k %)))
If you know the nested path then use get-in.
=> (get-in m [:a :d :f])
=> "f"
See here for details: https://clojuredocs.org/clojure.core/get-in
If you don't know the path in your nested structure you could write a function that recurses through the nested map looking for the particular key in question and either returns its value when it finds the first one or returns all the values for :f in a seq.
If you know the "path", consider using get-in:
(get-in m [:a :d :f]) ; => "f"
If the "path" is unknown you can use something like next function:
(defn find-in [m k]
(if (map? m)
(let [v (m k)]
(->> m
vals
(map #(find-in % k)) ; Search in "child" maps
(cons v) ; Add result from current level
(filter (complement nil?))
first))))
(find-in m :f) ; "f"
(find-in m :d) ; {:e "e", :f "f"}
Note: given function will find only the first occurrence.
Here is a version that will find the key without knowing the path to it. If there are multiple matching keys, only one will be returned:
(defn find-key [m k]
(loop [m' m]
(when (seq m')
(if-let [v (get m' k)]
v
(recur (reduce merge
(map (fn [[_ v]]
(when (map? v) v))
m')))))))
If you require all values you can use:
(defn merge-map-vals [m]
(reduce (partial merge-with vector)
(map (fn [[_ v]]
(when (map? v) v))
m)))
(defn find-key [m k]
(flatten
(nfirst
(drop-while first
(iterate (fn [[m' acc]]
(if (seq m')
(if-let [v (get m' k)]
[(merge-map-vals m') (conj acc v)]
[(merge-map-vals m') acc])
[nil acc]))
[m []])))))

how can you interleave two vectors of differing lengths in clojure

What is the simplest way to interleave two vectors with n+1 and n members?
(def a [:a :c :e])
(def b [:b :d])
(interleave a b ); truncates to shortest list
[:a :b :c :d]
;what I would like.
(interleave-until-nil a b)
[:a :b :c :d :e]
Cons the first, interleave the rest with arguments reversed.
(cons (first a) (interleave b (rest a)))
;=> (:a :b :c :d :e)
Conj nil to the second, interleave colls get all butlast
(butlast (interleave a (conj b nil)))
;=> (:a :b :c :d :e)
(defn interleave+ [& x]
(take (* (count x) (apply max (map count x)))
(apply interleave (map cycle x))))
(butlast (interleave+ [:a :c :e] [:b :d]))
=> (:a :b :c :d :e)
Tried this as an exercise in lazy seqs. I suspect that there are more elegant ways though.
(defn interleave-all
"interleaves including remainder of longer seqs."
[& seqs]
(if (not-empty (first seqs))
(cons (first (first seqs)) (lazy-seq (apply interleave-all (filter not-empty (concat (rest seqs) [(rest (first seqs))])))))))
If you would like to have nil appended to always have same dimension results, this could be a way to do that:
(defn interleave-all [& seqs]
(reduce
(fn [a i]
(into a (map #(get % i) seqs)))
[]
(range (apply max (map count seqs)))))
For example:
(interleave-all [:a] [:b :c])
outputs:
[:a :b nil :c]
This can be used to transpose a matrix:
(defn matrix-transpose [input]
(partition
(count input)
(apply interleave-all input)))
Example:
(matrix-transpose [[:a] [:b :c]])
Outputs:
[[:a :b] [nil :c]]
Which can be used to i.e. tabular output of lists of differing lengths (but where you need the fixed dimensions to insert nothing where lists have no value for certain indices).

Can I refer to a clojure hashmap value from another value in the same map?

I'm trying to come up with some way for the values in a clojure hashmap to refer to each other. Conceptually, something like this:
(def m {:a 1 :b 5 :c (+ (:a m) (:b m))} ;Implies (= (:c m) 6)
This doesn't work, of course since I'm circularly referencing m. I can do something like
(def m {:a 1 :b 5 :c (fn [a b] (+ a b))})
((:c m) (:a m) (:b m)) ;=> 6
but that doesn't really gain anything because I still have to know which a and b to put into the function. Another attempt:
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
((:c m) m) ;=> 6
It's a bit better since I've now internalized the function to a map though not specifically this map. I might try to fix that with something like this
(defn new-get [k m]
(let [v-or-fn (get m k)]
(if (fn? v-or-fn) (v-or-fn m) v-or-fn)))
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
(new-get :a m) ;=> 1
(new-get :b m) ;=> 5
(new-get :c m) ;=> 6
I think this is about the best I can do. Am I missing something more clever?
Couldn't help myself from writing a macro:
(defmacro defmap [name m]
(let [mm (into [] (map (fn [[k v]] `[~k (fn [~name] ~v)]) m))]
`(def ~name
(loop [result# {} mp# (seq ~mm)]
(if (seq mp#)
(let [[k# v#] (first mp#)]
(recur (assoc result# k# (v# result#)) (rest mp#)))
result#)))))
(defmap m [[:a 1]
[:b 5]
[:c (+ (:a m) (:b m))]])
;; m is {:a 1 :b 5 :c 6}
As I've already said in comment above you can use let form:
(def m
(let [a 1 b 5]
{:a a :b b :c (+ a b)}))
This should be fine if you're using values that known only inside m definition. Otherwise you would better to use function parameters as #Michiel shown.
P.S. by the way you're free to use everything inside def you're usually use in clojure. Moreover, sometimes you're free to use let in sugared form inside some other forms (although this let uses different mechanisms than usual let form):
(for [x (...) xs]
:let [y (+ x 1)]
; ...
Since c is a derived value, so a function, of a and b you're probably better of by defining a function that produces this map:
(defn my-map-fn [a b]
{:a a :b b :c (+ a b)})
(def my-map (my-map-fn 1 2))
(:c my-map) ;;=> 3
Here is my take on it:
(defmacro let-map [& bindings]
(let [symbol-keys (->> bindings (partition 2) (map first))]
`(let [~#bindings]
(into {} ~(mapv (fn [k] [(keyword k) k]) symbol-keys)))))
;; if you view it as similar to let, when it's more complicated:
(let-map
a 1
b 5
c (+ a b)) ; => {:a 1, :b 5, :c 6}
;; if you see it as an augmented hash-map, when it's simple enough:
(let-map a 1, b 5, c (+ a b)) ; => {:a 1, :b 5, :c 6}