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

在Clojure中,如何合并两个地图向量?,clojure,merge,state,clojurescript,Clojure,Merge,State,Clojurescript,我有一个实体向量(映射),我已经将这个向量过滤到那些具有特定键的实体。然后我应用一个函数来更新这些实体中的一些值,现在有了一个子集,我想与原始超集合并 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

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

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's可以获取多个序列,但不会将我的子集
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}}}
看起来类似,但未正确合并向量


真实世界的实施

我的实际代码是熟悉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))
系统/更新是:

(defn update! [world]
  (let [new-world (->> @world
                      (phys/move @player/input-cmd))]
    (player/clear-cmd)
    (reset! world new-world)
    @world))
我的计划是让一系列系统在线程最后一个宏中更新世界。我正在写
phys/move

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

我正在考虑是否更容易管理
系统/更新功能管理世界上的效果(应用更新)。因此,系统只返回它们的更改列表。比如:

(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))
(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))
重复的
(conj更新(func-world))
感觉很笨重。这可能比在系统函数中合并更新(或返回修改的实体)要复杂得多

如何在我的系统功能(
phys/move
camera/track
)和子系统功能(
walk
jump
)之间优雅地传递状态更改

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

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

这比所有这些都要简单(拥有一个地图向量是很常见的事情,它可能适合您的问题)。不要先进行筛选,然后再进行映射,只需执行映射即可:

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

(defn update-world [world]
  (let [update #(if (matches? %) (update-entity %) %)]
    (map update world)))
这种更新内容或保持原样的模式非常常见,习惯用法是让更新内容的函数返回更新的内容,或者如果它什么都没做,返回旧的内容,因此最后会是这样的:

(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))
(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))
你可以在这个蛇游戏的游戏逻辑中看到这种行为的一些例子:



我对单一地图方法唯一的疑问是在更新实体之前过滤实体。我可以过滤一个集合,但它更多的是使用一个地图?使用一个地图,你可以生成一个可移动实体的键过滤序列。(当你把一个映射作为一个序列使用时,它本身就是一个
[键值]
对的序列。)一个小问题:当让
在函数
中移动时
总是成功的-
()
是真实的。您可能想将
动产的表达式
封装在
seq
中。谢谢,我确实这样做了。根据您的更新,@Jouquin's是正确的答案。恐怕我搞错了。谢谢,我一直在查看您的cnake repo以了解实施想法。您在哪里进行了
更新?
,然后调用
#(如果(匹配?%)…
应该是
#(如果(更新?%)…