Search code examples
clojure

Dynamically create map using vector


I have a vector ["x" "y" "z"].

I am trying to dynamically create the following:

{:aggs {:bucket-aggregation
        {:terms {:field "x"},
         :aggs {:bucket-aggregation 
                {:terms {:field "y"}, 
                 :aggs {:bucket-aggregation 
                        {:terms {:field "z"}}}}}}}}

I currently have the following but can't figure out how to make it recursive

(defn testing [terms]
  {:aggs {:bucket-aggregation 
           {:terms {:field (nth terms 0)} (testing (pop terms))}}})

Solution

  • Here's one way to solve:

    (def my-vec ["x" "y" "z"])
    
    (defn testing [[head & tail]]
      (when head
        {:aggs {:bucket-aggregation (merge {:terms {:field head}}
                                           (testing tail))}}))
    
    (testing my-vec)
    ;=>
    ;{:aggs {:bucket-aggregation {:terms {:field "x"},
    ;                             :aggs {:bucket-aggregation {:terms {:field "y"},
    ;                                                         :aggs {:bucket-aggregation {:terms {:field "z"}}}}}}}}
    

    This works by destructuring the input into a head element and tail elements, so each call is adding a :field of head and recursing on the tail.

    And here's another way to solve using reduce:

    (reduce
     (fn [acc elem]
       {:aggs {:bucket-aggregation (merge {:terms {:field elem}} acc)}})
     nil
     (reverse my-vec))
    

    This works by reverseing the input vector and building the map from the inside-out. This reduce approach won't result in a stack overflow for large vectors, but the first solution will.