Vectors of maps: building paths, or can I detect duplicate keys?

My implementation below isn’t the cleanest, but here’s my solution:

I build a hashmap of the keywords in the vector and their indexes. Part of that building is a check if the key already exists, and if so, throw an informative exception.

I made an example function that specifically works for price, but could easily be made more generic. A very important detail I learned is that assoc can replace a value at an index with (assoc coll idx new-val). That’ll likely resolve your performance concerns.

(defn build-map-from-vec2 [value-vecs]
  (reduce (fn [acc v]
            (let [key (first v)
                  idx (second v)]
              (when (contains? acc key)
                (throw (Exception. (str "Key "
                                        key
                                        " already exists at index "
                                        (get acc key)
                                        " (duplicate key at "
                                        idx
                                        ")"))))
              (assoc acc key idx)))
          {}
          value-vecs))

(defn map-keyword-indexes [m]
  (->> m
       (map-indexed (fn [i v]
                      (if (keyword? v)
                        [v i]
                        nil)))
       (filter (complement nil?))
       build-map-from-vec2))

(defn replace-price [data new-price]
  (let [keyword-indexes (map-keyword-indexes data)]
    (if (contains? keyword-indexes :price)
      (let [price-idx (inc (:price keyword-indexes))
            old-price-map (nth data price-idx)
            new-price-map (assoc old-price-map :value new-price)]
        (assoc data price-idx new-price-map))
      (concat data [:price {:type :number
                            :value new-price}]))))

;; replaces existing price
(replace-price [:name {:type :text
                       :placeholder "Name here"}
                :price {:type :number
                        :read-only true
                        :value 50}]
               60)

;; [:name {:type :text,
;;         :placeholder "Name here"}
;;  :price {:type :number,
;;          :read-only true,
;;          :value 60}]



;; adds price if it doesn't exist
(replace-price [:name {:type :text
                       :placeholder "Name here"}]
               60)

;; [:name {:type :text,
;;         :placeholder "Name here"}
;;  :price {:type :number,
;;          :value 60}]



;; helper function throws exception for duplicate keys
(map-keyword-indexes [:name {:type :text
                             :placeholder "Name here"}
                      :price {:type :number
                              :read-only true
                              :value 50}
                      :name {:type :text
                             :value "New value"}])
;; Key :name already exists at index 0 (duplicate key at 4)
1 Like