How to write a dissoc-in command for clojure?

12,823

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

Solution 2

I write this using update-in:

(update-in {:a {:b 0 :c 1}} [:a] dissoc :b)

=>

{:a {:c 1}}

Solution 3

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

Solution 4

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]) 
;;=> {}    

Solution 5

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"}]}
Share:
12,823

Related videos on Youtube

zcaudate
Author by

zcaudate

Updated on June 19, 2022

Comments

  • zcaudate
    zcaudate almost 2 years

    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

  • 0dB
    0dB over 11 years
    Indeed, people have their own preferences whether empty maps should be removed or not, this is being discussed. In my use cases so far, I have chosen the original clojure.contrib.core code (quoted by Dominic above) which removes empty maps).
  • Alan Thompson
    Alan Thompson over 9 years
    Docs are at: github.com/clojure/core.incubator Current lein coords are [org.clojure/core.incubator "0.1.3"]. In your require use something like " [clojure.core.incubator :as cci]", then in program: (cci/dissoc-in {:a {:aa 1} :b {:bb 2}} [:a :aa]) yields {:b {:bb 2}}
  • oskarkv
    oskarkv over 9 years
    @0dB I just had a bug caused by removing empty maps. One of the maps was a priority map, not a regular map, so you can imagine what happened when I tried to use it later. (-> some-map (dissoc-in [:prio-map 123]) (assoc-in [:prio-map 234] something))
  • eyelidlessness
    eyelidlessness over 9 years
    Could use (drop-last p) instead of (take...). (Or butlast, but that may be discouraged.)
  • DimaSan
    DimaSan over 7 years
    While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
  • wedesoft
    wedesoft about 3 years
    Nice solution. Note that a special case is needed for a path-length of one. ``` (defn dissoc-in "Return nested hash with path removed" [m ks] (let [path (butlast ks) node (last ks)] (if (empty? path) (dissoc m node) (update-in m path dissoc node)))) ```