2014-09-04 51 views
1

什么是更新矢量内的地图的clojure方式如果我有这样的事情,假设每个地图都有独特的:nameclojure的方式来更新矢量内的地图

(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) 

但通过整个向量这将循环,即使我只更新一个项目。

+2

因为它是一个载体,需要循环遍吧,找你的元素,或者您可以knoy它的指数和指数接取它。你可以停止循环,当你发现你的元素,如果你知道它不会出现两次。可能只是矢量不是你的数据类型,尝试将其转换为映射和按键访问。 – coredump 2014-09-04 12:06:09

回答

6

保留数据作为地图而不是地图记录的向量,以: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

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

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

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

然后你可以用update-in更新?

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的地图)。

0

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

我可以提出此解决方案:

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

谢谢,马克。我会去'(减少#(assoc-by-fn%1:name%2){} some-vector)'来加载地图。这可能值得通过':name'参数来结束,这样表格就可以被一个函数操纵。 – Thumbnail 2014-09-04 13:51:01