【问题标题】:Clojure; How to iterate through a vector of maps, so that the index is known, to be able to update-in the map?克洛朱尔;如何遍历地图向量,以便知道索引,以便能够在地图中更新?
【发布时间】:2017-08-08 07:10:04
【问题描述】:

假设我将这个原子作为游戏的状态:

(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 值在 :cells 向量中)使用它们应该在下一帧中的值进行计算,然后在计算时更新新的: 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 来获得索引?

或者我是在接近这个完全不惯用的方法吗?

【问题讨论】:

标签: vector clojure hashmap


【解决方案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 用来分割原始向量并包含新的哈希映射对象。

【讨论】:

  • 感谢您提供这些替代方案!换句话说,没有 clojure 核心函数,它允许我通过给我迭代的索引来“迭代”一个向量?
  • 是的。你所描述的正是map-indexed 所做的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-06
  • 2018-01-13
  • 2018-10-15
  • 1970-01-01
  • 2012-09-11
  • 1970-01-01
相关资源
最近更新 更多