【问题标题】:clojure way to update a map inside a vectorclojure 方法来更新矢量内的地图
【发布时间】:2014-09-04 11:29:40
【问题描述】:

更新向量内的地图的 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)

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

【问题讨论】:

  • 因为它是一个向量,所以你需要遍历它来找到你的元素,或者你可能知道它的索引并通过索引访问它。你可以停止循环,当你找到你的元素时,如果你知道它不会出现两次。可能是矢量不是您的数据类型,请尝试将其转换为映射并按键访问。

标签: clojure


【解决方案1】:

将数据保存为地图,而不是地图记录向量,以: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"}}

【讨论】:

    【解决方案2】:

    鉴于您有地图矢量,您的代码对我来说看起来不错。您对“循环遍历整个向量”的担忧是您对 :name 进行线性搜索以及向量是不可变的事实的自然结果。

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

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

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

    【讨论】:

      【解决方案3】:

      鉴于输入数据的这种形状,除非您有一个索引可以告诉您具有给定值 :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))))
      

      这将执行与xspred 返回真值的值一样多的assoc 操作。

      例子:

      (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}]
      

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

      【讨论】:

        【解决方案4】:

        如果你使用向量,你应该知道你想要改变的元素的索引,否则你必须以某种方式遍历它。

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

        (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"}}
        

        【讨论】:

        • 谢谢,马克。我会去(reduce #(assoc-by-fn %1 :name %2) {} some-vector) 加载地图。可能值得关闭 :name 参数,以便表始终由单个函数操作。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多