Jay Fields is a software developer at DRW Trading. He has a passion for discovering and maturing innovative solutions. His most recent work has been in the Domain Specific Language space where he's delivered applications that empowered subject matter experts to write the business rules of the applications. He is also very interested in maturing software design through software testing. Jay is a DZone MVB and is not an employee of DZone and has posted 115 posts at DZone. You can read more from them at their website. View Full User Profile

Clojure: Get All Nested Map Values (N levels deep)

05.10.2011
| 4678 views |
  • submit to reddit

I recently needed to pull all the values from a nested map. The map I was working with was designed to give easy access to data like so (formatted):

user=>
(def my-data {"Jay" 
                {"clojure" 
                  {:name "Jay", :language "clojure", :enjoys true}, 
                 "ruby" 
                  {:name "Jay", :language "ruby", :enjoys true}} 
              "Jon" 
                {"java" 
                  {:name "Jon", :language "java", :enjoys true}}} )
#'user/my-data
user=> (get-in my-data ["Jon" "java"])                                                                                                             
 {:name "Jon", :language "java", :enjoys true}            
user=> (get-in my-data ["Jay" "ruby"])                                                                                                             
 {:name "Jay", :language "ruby", :enjoys true} 
This worked for all of the ways the data was accessed, until someone asked for a list of all people and all languages.

I needed a function that grabbed all the values nested to a point in the map (grabbing only the values instead of the deepest map wouldn't have provided valuable information). I created the following function that should grab all the values up to the nesting level specified.
(defn nth-vals* [a i m]
  (if (and (map? m) (> i 0))
    (reduce into a (map (fn [v] (nth-vals* a (dec i) v)) (vals m)))
    (conj a m)))

(defn nth-vals [i m]
  (if (nil? m)
    {}
    (nth-vals* [] i m)))
The nth-vals function can be used as the following example shows. (assuming the same map for my-data) (formatted)
user=> (nth-vals 2 my-data)
[{:name "Jay", :language "clojure", :enjoys true} 
 {:name "Jay", :language "ruby", :enjoys true}
 {:name "Jon", :language "java", :enjoys true}]
For reference, here's what's returned if we reduce the map all the way to it's values.
user=> (nth-vals 3 my-data)
["Jay" "clojure" true "Jay" "ruby" true "Jon" "java" true]
That list of values may be helpful for someone else, but it wouldn't have solved our current problem.

And, if you're interested, here's what's returned if you ask for 1 level deep of values. (formatted, again)
user=> (nth-vals 1 my-data)
[{"clojure" {:name "Jay", :language "clojure", :enjoys true}, 
  "ruby" {:name "Jay", :language "ruby", :enjoys true}} 
 {"java" {:name "Jon", :language "java", :enjoys true}}]
I wouldn't at all be surprised if something like this already exists in Clojure core, but I haven't found it yet.

 

From http://blog.jayfields.com/2011/05/clojure-get-all-nested-map-values-n.html

Published at DZone with permission of Jay Fields, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)