假设我将这个原子作为游戏的状态:
(defonce state (atom {:player
{:cells [{:x 123 :y 456 :radius: 1.7 :area 10}
{:x 456 :y 789 :radius: 1.7 :area 10}
{...}]}}))
我想要读取其中一个单元格(地图:x和:y值在:单元格向量中)计算它们应该在下一帧中的值,然后在计算时更新新的: x和:y在相应地图中的位置?
到目前为止,我有这个:
(let [cells (get-in @state [:player :cells])]
(reduce
(fn
[seq cell]
(let [cell-x (get-in cell [:x])
cell-y (get-in cell [:y])]
; do my calculations
; ...
))
nil
cells))
所以我可以读取值并进行计算但是如何使用新值更新x和y位置?我可以用:
(swap! state update-in [:player :cells ...] assoc :x new-x :y new-y)
但我不知道索引...在哪个向量中更新它。
我认为有一种方法可以不使用reduce来提供索引吗?
或者我是否正在接近这种完全不正常的事情?
答案 0 :(得分:1)
您可以在不知道向量中的索引的情况下更新特定的哈希映射对象:
(let [when-x 123
new-x -1
new-y -1]
(swap! state update-in [:player :cells]
(fn [v] (mapv (fn [{:keys [x y] :as m}]
(if (= x when-x)
(assoc m :x new-x :y new-y)
m))
v))))
;;=> {:player {:cells [{:x -1, :y -1, :radius 1.7, :area 10}
;; {:x 456, :y 789, :radius 1.7, :area 10}]}}
如果您有一些可能需要更新可能需要更新值的标准,此代码非常有用。请注意,这里我们不需要知道要执行更新的索引。
现在进行一次轻微的游览,但是如果你只需要更新一个特定的已知索引,那么一种方法是使用相同的技术但使用map-indexed
代替mapv
:
(let [when-idx 1
new-x -1
new-y -1]
(swap! state update-in [:player :cells]
(fn [v] (vec (map-indexed (fn [n {:keys [x y] :as m}]
(if (= n when-idx)
(assoc m :x new-x :y new-y)
m))
v)))))
然而,对于您的数据而言,这将毫无意义,因为向量是一个关联集合,因此update-in
将能够通过索引进行选择:
(let [when-idx 0
new-x -1
new-y -1]
(swap! state update-in [:player :cells when-idx] #(assoc % :x new-x :y new-y)))
有趣的是,如果你有一个列表而不是向量,那么'({:x 123 :y 456 :radius 1.7 :area 10}{:x 456 :y 789 :radius 1.7 :area 10})
就不会毫无意义。使用此非关联集合,您无法使用update-in
。
这种结构不会毫无意义的另一个原因是,如果你担心性能:你可以使用懒惰来短路找到答案:
(defn replace-in [v [idx new-val]]
(concat (subvec v 0 idx)
[new-val]
(subvec v (inc idx))))
(let [when-x 123
new-x -1
new-y -1]
(swap! state update-in [:player :cells]
(fn [v] (->> v
(keep-indexed (fn [idx {:keys [x] :as m}]
(when (= x when-x)
[idx (assoc m :x new-x :y new-y)])))
first
(replace-in v)))))
keep-indexed
与map-indexed
类似,只是任何nil
值都不会返回到输出序列中。一旦实现first
值,就不会产生其余的潜在值,因此会产生短路。调用idx
时,subvec
会使用namespace App\Http\Controllers;
来剪切原始矢量并包含新的哈希映射对象。