user8067719
user8067719

Reputation:

Clojure iteration and nesting data

Suppose i have some data like this:

{:gender "male" :name "Gabriel" :age 25}
{:gender "female" :name "Jane" :age 25}
{:gender "female" :name "Alice" :age 22}

How can i iterate through these data and nest them based in a hash so it can be something like this:

[:25 [{:gender "male" :name "Gabriel" :age 25}, {:gender "female" :name "Jane" :age 25}] :22[ {:gender "female" :name "Alice" :age 22} ]

I have already tried to use maps and vectors and i was able to iterate through the data using doseq but i'm having a lot of trouble to nest the data. Can anyone bring some light to this? Thank you very much.

Upvotes: 2

Views: 94

Answers (2)

Ivan Grishaev
Ivan Grishaev

Reputation: 1681

The code below gives exactly what you want:

(def data 
  [{:gender "male" :name "Gabriel" :age 25}
   {:gender "female" :name "Jane" :age 25}
   {:gender "female" :name "Alice" :age 22}])

(vec (mapcat identity (group-by :age data)))

[25 [{:gender "male", :name "Gabriel", :age 25} 
     {:gender "female", :name "Jane", :age 25}]
 22 [{:gender "female", :name "Alice", :age 22}]]

Upvotes: 0

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91534

A common pattern is to take the poblem and split it into parts, make an expression for each part then thread them together (->> is pronounced "thread last" and it passes the output of each expression to the next expression in the last argument position) In this case I see three stages:

  1. sort them by age to get the similar ages next to eachother
  2. split the sequence into chunks by age
  3. format each group into [age-in-group whole-group

Which can be written as:

user> (def my-data [{:gender "male" :name "Gabriel" :age 25}
                    {:gender "female" :name "Jane" :age 25}
                    {:gender "female" :name "Alice" :age 22}])
#'user/my-data
user> (->> my-data
           (sort-by :age)
           (partition-by :age)
           (map (fn [group] [(keyword (str (:age (first group)))) group]))
           reverse)
([:25 ({:gender "male", :name "Gabriel", :age 25}
       {:gender "female", :name "Jane", :age 25})]
 [:22 ({:gender "female", :name "Alice", :age 22})])

The reverse at the end is just to put it in descending order so it matches your desired output style. The important idea is to make several small expressions that each do part of the problem then compose them to solve the whole.

You may also want to know about the group-by function on maps, that takes a sequence of maps and very nearly solves your problem directly.

user> (group-by :age my-data)
{25 [{:gender "male", :name "Gabriel", :age 25} 
     {:gender "female", :name "Jane", :age 25}], 
 22 [{:gender "female", :name "Alice", :age 22}]}

It returns a map instead of sequence (or vector) and does not turn the keys into keywords. For practical reasons, it's likely the answer to most such problems. Again the important part is composing smaller solutions to make approaching the larger problem convenient.

Upvotes: 4

Related Questions