how to work with complex data in Clojure edn/map

167 Views Asked by At
(def dbSample {
               :person                  [{:person/id 1 :name "dan" :surname "stone" :joindate "01.06.2022" :experience :experience/starter :loyalitylevel :loyality-level/zero-one-years :worktime :worktime/internship :managers {:manager/id 1}}
                                         {:person/id 2 :name "dave" :surname "jhons" :joindate "04.04.2021" :experience :experience/medior :loyalitylevel :loyality-level/one-two-years :worktime :worktime/full-time :managers {:manager/id 1}}
                                         {:person/id 3 :name "patrick" :surname "square pant" :joindate "09.01.2022" :experience :experience/senior :loyalitylevel :loyality-level/zero-one-years :worktime :worktime/part-time :managers {:manager/id 1}}
                                         {:person/id 9 :name "rich" :surname "hickey" :joindate "04.04.2016" :experience :experience/lead :loyalitylevel :loyality-level/more-than-seven-years :worktime :worktime/part-time}
                                        ]

               :employees/developerteam [{:frontend  [[:person/id 1] [:person/id 2] [:person/id 3]]
                                         }]

               :employees/managers      [{
                                          :manager/id 1 :manager/person [:person/id 9]
                                          }
                                         ]

               :relations/experience    {:experience/starter "0-1"
                                         :experience/medior  "1-3"
                                         :experience/senior  "3-6"
                                         :experience/lead    "6+"
                                         }

               :relations/loyalitylevel {:loyality-level/zero-one-years        "0-1 year(s)"
                                         :loyality-level/one-two-years         "1-2 year(s)"
                                         :loyality-level/three-five-years      "3-5 year(s)"
                                         :loyality-level/five-seven-years      "5-7 year(s)"
                                         :loyality-level/more-than-seven-years "7+ year(s)"}

               :relations/worktime      {:worktime/full-time  "full time"
                                         :worktime/part-time  "part time"
                                         :worktime/internship "internship"}}
  )

Hello everyone, I started to learn Clojure and to try to do some exercises to understand it better. How can I find the frontend team member's manager's name? I getting their ":person/id" with that function that you can see at the bottom. Now I wondering how can I reach their manager's names.

I just created a sample data (it is little bit more complex but I cut off extra complexity) to learn how to get data and travel inside data. I want to give input to my second function's output and I want to see who are these person managers. I don't know if it is easy or possible.

(defn getFrontendDeveloperTeamMembersIds [] :docstring "get frontend developer team members references"
 (->> dbSample
      (:employees/developerteam)
      (first)
      (:frontend)
      )
 )
(getFrontendDeveloperTeamMembersIds)
;=> [[:person/id 1] [:person/id 2] [:person/id 3]]

I created some function to help my need, they are almost working by I am not sure that they are best practices or not. Moreover still I giving :person/id manually.


(defn getPersonById [id] :docstring "get person by :person/id value"
 (->> dbSample
      (:person)
      (filter #(= (:person/id %1) id))))

(defn getManagerPersonIdByManagerId [id] :docstring "get a manager name my given manager id"
 (->> dbSample
      (:employees/managers)
      (filter #(= (:manager/id %) id))
      (first)
      (:manager/person)
      (second)
      ))

(defn getPersonNameById [id]
 (->> dbSample
      (:person)
      (filter #(= (:person/id %1) id))
      (first)
      (:name)
      )
 )

(defn getPersonManagerNameByPersonId [id] :docstring "get a person's manager name by given person id"
 (->> id
      (getPersonById)
      (first)
      (:managers)
      (:manager/id)
      (getManagerPersonIdByManagerId)
      (getPersonNameById)
      )
 )



(getPersonManagerNameByPersonId 1)
;=> "rich"

2

There are 2 best solutions below

1
On

There are many ways to do this but you asked for guidance on best practices so here are a couple (but I make some assumptions)

Assumption 1: the person/id uniquely identifies a person. Probably right since you use first. So rather than a list use a dict for person.

Similarly, managers and teams can be the dict you show without using a vector.

so data like:

(def db-sample {
           :person                  {1 {:person/id 1 :name "dan" :surname "stone" :joindate "01.06.2022" :experience :experience/starter :loyalitylevel :loyality-level/zero-one-years :worktime :worktime/internship :managers {:manager/id 1}}
                                     2 {:person/id 2 :name "dave" :surname "jhons" :joindate "04.04.2021" :experience :experience/medior :loyalitylevel :loyality-level/one-two-years :worktime :worktime/full-time :managers {:manager/id 1}}
                                     3 {:person/id 3 :name "patrick" :surname "square pant" :joindate "09.01.2022" :experience :experience/senior :loyalitylevel :loyality-level/zero-one-years :worktime :worktime/part-time :managers {:manager/id 1}}
                                     9 {:person/id 9 :name "rich" :surname "hickey" :joindate "04.04.2016" :experience :experience/lead :loyalitylevel :loyality-level/more-than-seven-years :worktime :worktime/part-time}
                                    }

            :employees/developerteam {:frontend  [[:person/id 1] [:person/id 2] [:person/id 3]]}

           :employees/managers      {:manager/id 1 :manager/person [:person/id 9]}

           :relations/experience    {:experience/starter "0-1"
                                     :experience/medior  "1-3"
                                     :experience/senior  "3-6"
                                     :experience/lead    "6+"
                                     }

           :relations/loyalitylevel {:loyality-level/zero-one-years        "0-1 year(s)"
                                     :loyality-level/one-two-years         "1-2 year(s)"
                                     :loyality-level/three-five-years      "3-5 year(s)"
                                     :loyality-level/five-seven-years      "5-7 year(s)"
                                     :loyality-level/more-than-seven-years "7+ year(s)"}

           :relations/worktime      {:worktime/full-time  "full time"
                                     :worktime/part-time  "part time"
                                     :worktime/internship "internship"}})

Now you can query much more of the data with simple get-in, e.g. :

(defn get-person-by-id
  "get person by :person/id value" 
  [id]
  (get-in db-sample [:person id]))

Also note the position of the docstring and preference for kebab-case over camelCase. Databases change so the db should be passed into the queries to be pure functions.

1
On

this task seems to be much alike the database inner join functionality, when you have to join :employees/developerteam -> :frontenders with :person then with :employees/managers and then with :person again to get the person (manager) to person (frontender) mapping. So for educational reasons i would propose to makeup this join function.. It could look like this:

(defn inner-join [[& {from :from alias1 :as}] & clauses]
  (reduce (fn [join-agg [next-table & {:keys [as on]}]]
            (for [a join-agg
                  b next-table
                  :let [res (assoc a as b)]
                  :when (on res)]
              res))
          (map #(hash-map alias1 %) from)
          clauses))

For example, the first pair join of frontender to person would look like this:

(inner-join
 [:from (get-in db-sample [:employees/developerteam 0 :frontend]) 
  :as :frontender]
 [(:person db-sample) 
  :as :person 
  :on (fn [{:keys [frontender person]}]
        (= (second frontender) (:person/id person)))])

resulting to this:

({:person
  {:person/id 1,
   ;; omitted
   :managers #:manager{:id 1}},
  :frontender [:person/id 1]}
 {:person
  {:person/id 2,
   ;; omitted
   :managers #:manager{:id 1}},
  :frontender [:person/id 2]}
 {:person
  {:person/id 3,
   ;; omitted
   :worktime :worktime/part-time,
   :managers #:manager{:id 1}},
  :frontender [:person/id 3]})

The same manner you could continue until you get your solution:

(inner-join
 [:from (get-in db-sample [:employees/developerteam 0 :frontend]) 
  :as :frontender]
 [(:person db-sample) 
  :as :person 
  :on (fn [{:keys [frontender person]}]
        (= (second frontender) (:person/id person)))]
 [(:employees/managers db-sample) 
  :as :person-manager 
  :on (fn [{:keys [person person-manager]}]
        (= (-> person :managers :manager/id) (:manager/id person-manager)))]
 [(:person db-sample) 
  :as :manager
  :on (fn [{:keys [manager person-manager]}]
        (= (-> person-manager :manager/person second)
           (:person/id manager)))])

which results to:

({:person-manager #:manager{:id 1, :person [:person/id 9]},
  :person
  {:person/id 1,
   :name "dan",
   :surname "stone",
   :joindate "01.06.2022",
   :experience :experience/starter,
   :loyalitylevel :loyality-level/zero-one-years,
   :worktime :worktime/internship,
   :managers #:manager{:id 1}},
  :frontender [:person/id 1],
  :manager
  {:person/id 9,
   :name "rich",
   :surname "hickey",
   :joindate "04.04.2016",
   :experience :experience/lead,
   :loyalitylevel :loyality-level/more-than-seven-years,
   :worktime :worktime/part-time}}
 {:person-manager #:manager{:id 1, :person [:person/id 9]},
  :person
  {:person/id 2,
   :name "dave",
   :surname "jhons",
   :joindate "04.04.2021",
   :experience :experience/medior,
   :loyalitylevel :loyality-level/one-two-years,
   :worktime :worktime/full-time,
   :managers #:manager{:id 1}},
  :frontender [:person/id 2],
  :manager
  {:person/id 9,
   :name "rich",
   :surname "hickey",
   :joindate "04.04.2016",
   :experience :experience/lead,
   :loyalitylevel :loyality-level/more-than-seven-years,
   :worktime :worktime/part-time}}
 {:person-manager #:manager{:id 1, :person [:person/id 9]},
  :person
  {:person/id 3,
   :name "patrick",
   :surname "square pant",
   :joindate "09.01.2022",
   :experience :experience/senior,
   :loyalitylevel :loyality-level/zero-one-years,
   :worktime :worktime/part-time,
   :managers #:manager{:id 1}},
  :frontender [:person/id 3],
  :manager
  {:person/id 9,
   :name "rich",
   :surname "hickey",
   :joindate "04.04.2016",
   :experience :experience/lead,
   :loyalitylevel :loyality-level/more-than-seven-years,
   :worktime :worktime/part-time}})

now you can select only items you need, or remove intermediate stuff.. or whatever you want.