Grouping and nested keys in a hash

253 views
Skip to first unread message

James Conroy-Finn

unread,
Jan 16, 2014, 8:46:46 AM1/16/14
to clo...@googlegroups.com
Hello all,

I've found myself stuck trying to move matching keys in a hash into a nested hash.

For example,:

{:a--id 1 :a--name "A" :b--id 2 :b--name "B"} ;=> {:a {:id 1 :name "A"} :b {:id 2 :name "B"}}

I've tried `clojure.walk`, I've tried building the args to `assoc-in`, and using `apply`. I honestly feel like I'm missing something really obvious, like something with group-by.

Any tips would be greatly appreciated. I'll keep hammering away.

Jim - FooBar();

unread,
Jan 16, 2014, 8:58:45 AM1/16/14
to clo...@googlegroups.com
You could do something like this:


(def v {:a--id 1 :a--name "A" :b--id 2 :b--name "B"})

(group-by #(-> % first str second str keyword) v)

=>{:b [[:b--name "B"] [:b--id 2]], :a [[:a--id 1] [:a--name "A"]]}

(reduce-kv #(assoc % %2 (into {} %3)) {} *1)

=> {:a {:a--id 1, :a--name "A"}, :b {:b--name "B", :b--id 2}}

all that's left is the names in the inner map...

HTH,

Jim
> --
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient
> with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to clojure+u...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

James Conroy-Finn

unread,
Jan 16, 2014, 9:36:45 AM1/16/14
to clo...@googlegroups.com
Thanks for the pointers. I've gotten as far as grouping the keys by the prefix using `group-by`, and added clunky support for keys that aren't nested like so:

``` clj
(defn- nested-key [k]
  (some-> (re-matches #"^(.*)--(.*)$" (name k))
          second
          keyword))

(defn nest-relations
  "Walks the given hash-map, and moves any keys of the form
  <group>--<key> into a nested hash-map."
  [m]
  (let [grouped (group-by (fn [[k _]] (or (nested-key k) ::top)) m)
        top (::top grouped)]
    (-> grouped
        (dissoc ::top)
        (merge top))))
```

I end up with vectors instead of hash-maps, and I'm particularly happy with the code, but it's getting there.

James

unread,
Jan 16, 2014, 9:40:24 AM1/16/14
to clo...@googlegroups.com
Whoops… that's missing an all important (merge (into {} top)).

Jan Herich

unread,
Jan 16, 2014, 9:45:48 AM1/16/14
to clo...@googlegroups.com
Another way of doing that:

(def data {:b--name "B", :a--id 1, :b--id 2, :a--name "A"})
 
(defn transform [data delimiter]
(reduce (fn [acc [k v]]
(let [[f-k s-k] (-> k (name) (str/split delimiter))]
(assoc-in acc [(keyword f-k) (keyword s-k)] v))) {} s))
 
(transform data #"--") ;; {:a {:name "A", :id 1}, :b {:id 2, :name "B"}}

Dňa štvrtok, 16. januára 2014 14:46:46 UTC+1 James Conroy-Finn napísal(-a):

James

unread,
Jan 16, 2014, 9:49:15 AM1/16/14
to clo...@googlegroups.com
And here is the final piece of working code, which we're using to transform results from Korma when we join a has one or belongs to association.

(def ^:private rel-attribute-pattern #"(.*)--(.*)$")

(defn- nested-key [k]
  (some-> (re-matches rel-attribute-pattern (name k))
          second
          keyword))

(defn nest-relations
  "Walks the given hash-map, and moves any keys of the form
  <relation>--<attribute> into a nested hash-map."
  [m]
  (let [grouped (group-by (fn [[k _]] (or (nested-key k) ::top)) m)
        top (::top grouped)
        remove-rel #(str/replace % rel-attribute-pattern "$2")]
    (-> (reduce-kv
          #(assoc % %2 (util/update-keys (into {} %3) remove-rel)) {} grouped)
        (dissoc ::top)
        (merge (into {} top)))))

This can obviously be refactored and optimised, but does the job for now.

Thank you very much for your help Mr. foo.bar.

Jan Herich

unread,
Jan 16, 2014, 9:50:13 AM1/16/14
to clo...@googlegroups.com
My solution would work only for fixed 2 level deep maps, but it would be not so hard to modify it that it will be much more generic :)

Dňa štvrtok, 16. januára 2014 14:46:46 UTC+1 James napísal(-a):

Jan Herich

unread,
Jan 16, 2014, 9:53:10 AM1/16/14
to clo...@googlegroups.com
Here we go, more generic solution:

(defn transform [data delimiter]
(reduce (fn [acc [k v]]
(let [key-strs (-> k (name) (str/split delimiter))]
(assoc-in acc (mapv keyword key-strs) v))) {} s))

Dňa štvrtok, 16. januára 2014 14:46:46 UTC+1 James napísal(-a):
Hello all,

James

unread,
Jan 16, 2014, 9:54:47 AM1/16/14
to clo...@googlegroups.com
Wow, I feel like a clumsy fool bashing parens together.

Have you been using Clojure for a long time Jan? I ask because I often don't know where to begin when approaching a problem with Clojure, and it can be quite frustrating.

James Conroy-Finn

unread,
Jan 16, 2014, 9:56:18 AM1/16/14
to clo...@googlegroups.com
Thank you for sharing.

I think the final `s` needs to be `data`, right?

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/vvs5uAWYrBE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Jan Herich

unread,
Jan 16, 2014, 10:00:17 AM1/16/14
to clo...@googlegroups.com
I have been using Clojure for something more then 2 years, something more then 1 year intensive... 
But i think those feelings of frustration are absolutely OK, when i started with Clojure for the very first
time, i was so frustrated (years of OOP mind-bending...) that i gave up for some time and only later
i collected my courage and approached Clojure again. Determination and patience is very important.

Dňa štvrtok, 16. januára 2014 15:54:47 UTC+1 James napísal(-a):

Jan Herich

unread,
Jan 16, 2014, 10:01:08 AM1/16/14
to clo...@googlegroups.com
Oh, yes, my mistake, it should be data instead of s

Dňa štvrtok, 16. januára 2014 15:56:18 UTC+1 James napísal(-a):
Reply all
Reply to author
Forward
0 new messages