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)