用于更新向量内部地图的clojure方法

时间:2014-09-04 11:29:41

标签: clojure

更新矢量内地图的clojure方法是什么?如果我有这样的东西,假设每张地图都有唯一的:name

(def some-vec
  [{:name "foo"
    ....}
   {:name "bar"
    ....}
   {:name "baz"
    ....}])

如果:name等于foo,我想以某种方式更新地图。目前我正在使用map,就像这样

(map (fn [{:keys [name] :as value}]
       (if-not (= name "foo")
         value
         (do-something .....))) some-vec)

但即使我只更新了一个项目,这也会遍历整个矢量。

4 个答案:

答案 0 :(得分:7)

将数据保存为地图,而不是:name键入的地图记录矢量。

(def some-data
  {"foo" {:name "foo" :other :stuff}
   "bar" {:name "bar" :other :stuff}
   "baz" {:name "baz" :other :stuff}})

然后

(assoc-in some-data ["bar" :other] :things)

产生

{"foo" {:other :stuff, :name "foo"},
 "bar" {:other :things, :name "bar"},
 "baz" {:other :stuff, :name "baz"}}

一气呵成。

您可以在

中捕获基本操作
(defn assoc-by-fn [data keyfn datum]
  (assoc data (keyfn datum) datum))

例如,

(assoc-by-fn some-data :name {:name "zip" :other :fassner})

产生

{"zip" {:other :fassner, :name "zip"},
 "foo" {:other :stuff, :name "foo"},
 "bar" {:other :stuff, :name "bar"},
 "baz" {:other :stuff, :name "baz"}}

答案 1 :(得分:1)

鉴于你有一张地图矢量,你的代码对我来说很好。您关注"循环遍历整个矢量"是你对:name进行线性搜索以及向量是不可变的这一事实的自然结果。

我想知道你真正想要的是地图矢量吗?为什么不是地图地图?

(def some-map
  {"foo" {...}
   "bar" (...}
   "baz" {...}}

然后您可以使用update-in更新哪些内容?

答案 2 :(得分:1)

考虑到输入数据的这种形状,除非你有一个索引可以告诉你具有给定值:name的地图所在的索引,你将不得不循环遍历整个矢量。但是,您可以通过仅更新"来最小化生成更新向量所涉及的工作量。匹配的地图,而不是重建整个矢量:

(defn update-values-if
  "Assumes xs is a vector. Will update the values for which
  pred returns true."
  [xs pred f]
  (let [lim (count xs)]
    (loop [xs xs i 0]
      (if (< i lim)
        (let [x (nth xs i)]
          (recur (if (pred x)
                   (assoc xs i (f x))
                   xs)
                 (inc i)))
        xs))))

这将执行尽可能多的assoc次操作,因为xs中的值pred会返回真值。

示例:

(def some-vec [{:name "foo" :x 0} {:name "bar" :x 0} {:name "baz" :x 0}])

(update-values-if some-vec #(= "foo" (:name %)) #(update-in % [:x] inc))
;= [{:name "foo", :x 1} {:name "bar", :x 0} {:name "baz", :x 0}]

当然,如果您计划以这种方式有规律地转换矢量,那么缩略图和保罗建议使用地图将是一个更为显着的改进。如果:name没有唯一标识地图,情况仍然如此 - 在这种情况下,您可以使用frequencies简单地转换原始矢量并处理矢量地图(具有给定地图的地图) :name)。

答案 3 :(得分:0)

如果您正在使用vector,您应该知道要更改的元素的索引,否则您必须以某种方式遍历它。

我可以提出这个解决方案:

(defn my-update [coll val fnc & args]
  (let [index (->> (map-indexed vector coll)
                   (filter (fn [[_ {x :name}]] (= x val)))
                   ffirst)]
    (when index
      (apply update-in coll [index] fnc args))))

其中:
coll - 给出地图集; val - 字段:name的值; fnc - 更新功能; args - 更新函数的参数。

我们试一试:

user> (def some-vec
        [{:name "foo"}
         {:name "bar"}
         {:name "baz"}])
;; => #'user/some-vec
user> (my-update some-vec "foo" assoc :boo 12)
;; => [{:name "foo", :boo 12} {:name "bar"} {:name "baz"}]
user> (my-update some-vec "bar" assoc :wow "wow!")
;; => [{:name "foo"} {:name "bar", :wow "wow!"} {:name "baz"}]

我认为Thumbnail's answer可能对您非常有用。如果您可以将数据保存为地图,则这些操作变得更加容易。以下是将矢量转换为地图的方法:

user> (apply hash-map (interleave (map :name some-vec) some-vec))
;; => {"foo" {:name "foo"}, "bar" {:name "bar"}, "baz" {:name "baz"}}