在 Clojure 中,如何合并两个地图向量?

Posted

技术标签:

【中文标题】在 Clojure 中,如何合并两个地图向量?【英文标题】:In Clojure how can I merge two vectors of maps? 【发布时间】:2014-05-22 12:22:38 【问题描述】:

我有一个实体向量(地图),我已将此向量过滤到具有特定键的那些实体。然后我应用一个函数来更新这些实体中的一些值,现在有一个我想与原始超集合并的子集。

world     [a b c d e]
a         [b d e]            ; (filter matches? world)
b         [b' d' e']         ; (map func a)
new-world [a b' c d' e'] ; (merge world b)

如何将我的更新b 与原始world 合并?

我的地图结构是:

:id identity
 :attrs :property1 :param1 value
                     :param2 value
         :property2 

可以有可变数量的属性。属性可以有 0 () 或更多参数。地图中唯一发生变化的部分是值,而且只是其中的一部分。

我必须查看每张地图以识别它,然后相应地合并?这样做的惯用方法是什么?

Clojure 的 map function 可以采用多个序列,但不会将我的子集 b 与整个超集 world 进行比较,只有 b 的世界实体数量,并且在 b 用完后停止。

> (map #(str %1 " " %2) [1 2 3 4 5] [6 7 8])
("1 6" "2 7" "3 8")

我是否一开始就为我的数据选择了错误的结构?如果没有矢量和只有一张地图,我会更好吗?

:entid1
   :property1 :param1 value :param2 value
    :property2 
 :entid2
   :property1 :param1 value :param2 value
    :property2 :param1 value
    :property3 :param1 value :param2 value :param3 value

This question 看起来很相似,但没有正确合并我的向量。


现实世界的实施

我的实际代码是 part of a game I'm writing 以熟悉 Clojure(在本例中为 ClojureScript)。

游戏状态(世界)如下:

[:id :player,
  :attrs
    :gravity :weight 10,
     :jump :height 60, :falling false, :ground true,
     :renderable :width 37, :height 35,
     :position :x 60, :y 565,
     :walk :step 4, :facing :right,
     :input ,
     :solid :blocked false
 :id wall1,
  :attrs
    :solid ,
     :position :x 0, :y 0,
     :renderable :width 20, :height 600
 :id wall2,
  :attrs
    :solid ,
     :position :x 780, :y 0,
     :renderable :width 20, :height 600
 :id platform3,
  :attrs
    :solid ,
     :position :x 20, :y 430,
     :renderable :width 600, :height 20] 

我在每个动画帧上更新世界,然后重新渲染画布。

(defn game-loop []
  (ui/request-frame game-loop)
  (-> world
      system/update!
      ui/render))

system/update! 是:

(defn update! [world]
  (let [new-world (->> @world
                      (phys/move @player/input-cmd))]
    (player/clear-cmd)
    (reset! world new-world)
    @world))

我的计划是在线程最后一个宏中创建一个系统链来更新世界。我正在写phys/move

这意味着系统必须更新世界,而不是仅仅返回它们对世界的影响(变化向量)。

我正在考虑让system/update! 函数管理对世界的影响(应用更新)是否更易于管理。所以系统只返回他们的更改列表。比如:

(defn update! [world]
  (let [updates []
        game @world]
     (conj updates (phys/move @player/input-cmd game))
     (conj updates (camera/track :player game))
     ; etc.
     (reset! world
       (map #(update-world updates %) world))
     @world))

虽然重复(conj updates (func world)) 感觉很笨拙。这可能不仅仅是在系统函数中合并更新(或返回修改的实体)。

如何优雅地在我的系统函数(phys/movecamera/track)和子系统函数(walkjump)之间传递状态变化?

我尝试将 Joaquin 的仅地图方法应用于我的 move 系统:

(ns game.phys)

(def step 4)
(def height 50)

(defn walk?
  "is this a walk command?"
  [cmd]
  (#:left :right cmd))

(defn walks?
  "update? equivalent"
  [entity]
  (contains? (:attrs entity) :position))

(defn walk
  [cmd entity]
  (if (walks? entity)
    (let [x (get-in entity [:attrs :position :x]
          op (if (= cmd :left) - +)
          update :attrs :position :x (op x step)
                          :walk     :facing cmd]
      (merge entity update))
    entity))

; cmd is a deref'ed atom that holds player input commands
; e.g. :left :right: :jump
(defn move
  [cmd world]
  (cond
    (walk? cmd) (map #(walk input-cmd %) world)
    (jump? cmd) (map #(jump height %) world)
    :else world))

【问题讨论】:

我对单一地图方法的唯一疑问是在更新实体之前对其进行过滤。我可以过滤集合,但使用单个地图更有效? 使用地图,您可以为可移动实体生成过滤后的序列。 (当您将地图用作序列时,它会将自身呈现为[key value] 对的序列。) 一个小点:函数move 中的when-let 将永远成功-() 是真实的。您可能希望将 movables 的表达式包装在 seq 中。 谢谢,我确实做到了。 根据您的更新,@Jouquin 的答案是正确的。恐怕我抓住了错误的一端。 【参考方案1】:

它比这更简单(拥有地图矢量是很常见的,它可能适合您的问题)。与其先做过滤器再做地图,不如做一个地图:

(defn update? [entity] ...)
(defn update-entity [entity] ...)

(defn update-world [world]
  (let [update #(if (matches? %) (update-entity %) %)]
    (map update world)))

这种更新某些东西或保持原样的模式非常普遍,并且使更新某些东西的函数返回更新的东西,或者如果它什么都不做,则返回旧的东西是惯用的,所以最后它会是这样的:

(defn update-entity?
  "Predicate that returns if an entity needs updating"
  [entity] ...)

(defn update-entity
  "Updates an entity if it is needed.
   Otherwise returns the unchanged entity"
  [entity]
  (if (update-entity? entity)
    (assoc entity :something :changed)
    entity))

(defn update-world [world]
  (map update-entity world))

你可以在这个贪吃蛇游戏的游戏逻辑中看到一些这种行为的例子:

https://github.com/joakin/cnake/blob/master/src/cnake/game.cljs#L54-L66https://github.com/joakin/cnake/blob/master/src/cnake/game.cljs#L102-L114

【讨论】:

谢谢,我一直在查看您的 cnake repo 以获取实施想法。 你在哪里有update? 然后调用#(if (matches? %)... 应该是#(if (update? %)... 吗?

以上是关于在 Clojure 中,如何合并两个地图向量?的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 基于键的地图合并列表

Clojure let 绑定表单

如何在 Clojure 中递归展平任意嵌套的向量和映射?

Clojure 中的可插拔向量处理单元

Clojure / Clojurescript:按多个值的地图分组

在 Clojure 中添加向量的惯用方法是啥?