Thursday, September 30, 2010

Clojure: Flatten Keys

I recently needed to take a nested map and flatten the keys. After a bit of trial and error I came up with the following code. (note: the example expects Expectations)
(ns example
(:use expectations))

(defn flatten-keys* [a ks m]
(if (map? m)
(reduce into (map (fn [[k v]] (flatten-keys* a (conj ks k) v)) (seq m)))
(assoc a ks m)))

(defn flatten-keys [m] (flatten-keys* {} [] m))

(expect
{[:z] 1, [:a] 9, [:b :c] Double/NaN, [:b :d] 1, [:b :e] 2, [:b :f :g] 10, [:b :f :i] 22}
(flatten-keys {:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}))

As the test shows, the code converts
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
into
{[:z] 1, [:a] 9, [:b :c] Double/NaN, [:b :d] 1, [:b :e] 2, [:b :f :g] 10, [:b :f :i] 22}
Improvement suggestions welcome.

3 comments:

  1. I've had to do the same thing in a recent project and came up with basically the same code. The only difference was I made flatten-keys a multiple arity function so you wouldn't have to clutter your namespace with flatten-keys* and flatten-keys.

    ReplyDelete
  2. Anonymous3:47 PM

    this works really well -- the only adjustment i made was to the if statement to add a check for empty maps

    like so

    (if (and (map? m) (not (empty? m)))
    recur
    base case
    )

    ReplyDelete
  3. Anonymous3:48 PM

    this works well, the only adjustment i made was to check for empty maps -- so something like

    (if (and (map? m) (not (empty? m))) ... )

    ReplyDelete

Note: Only a member of this blog may post a comment.