简体   繁体   English

clojure-根据键值以不同方式更新地图值

[英]clojure - update map values in different ways based on key value

I want to represent a graph of 2D positions + neighbors in Clojure for a board game. 我想在棋盘游戏中用Clojure表示2D位置+邻居的图形。 I am using a map that maps a position to a vector of neighbors: 我正在使用将位置映射到邻居矢量的地图:

{[0 0] [[0 1] [1 0] [1 1]]}

I have written some functions that can generate the neighbor graph for any sized board: 我编写了一些函数,可以为任何大小的板生成邻居图:

(defn positions [size]
  (for [x (range 0 size) y (range 0 size)] [x y]))

(defn neighbors [size [x y]]
  (filter (fn [[x y]]
        (and (>= x 0) (< x size) (>= y 0) (< y size)))
      (-> []
          (conj [(inc x) y])
          (conj [(dec x) y])
          (conj [x (inc y)])
          (conj [x (dec y)])
          (conj [(inc x) (inc y)])
          (conj [(dec x) (dec x)]))))

(defn board-graph
  [size]
  (reduce (fn [map position] (assoc map
                                    position
                                    (neighbors size position)))
          {}
          (positions size)))

This works fine: 这工作正常:

(board-graph 2)
=> {[0 0] ([1 0] [0 1] [1 1]), [0 1] ([1 1] [0 0]), [1 0] ([0 0] [1 1] [0 0]), [1 1] ([0 1] [1 0] [0 0])}

However I now want to add to this additional 'virtual neighbors' to each of the board positions that are on the edge of the board, .eg :TOP, :BOTTOM, :LEFT, :RIGHT. 但是,我现在想在板边缘上的每个板位置上添加此额外的“虚拟邻居”,例如.TOP,:BOTTOM,:LEFT,:RIGHT。 So I'd like: 所以我想:

(board-graph 2)
=> {[0 0] (:LEFT :TOP [1 0] [0 1] [1 1]), [0 1] (:LEFT :BOTTOM [1 1] [0 0]), [1 0] (:RIGHT :TOP [0 0] [1 1] [0 0]), [1 1] (:RIGHT :BOTTOM [0 1] [1 0] [0 0])}

Here is my attempt so far, but it doesn't quite work right and it seems really over-complicated: 到目前为止,这是我的尝试,但效果不佳,似乎过于复杂:

(defn- filter-keys
  [pred map]
  (into {}
        (filter (fn [[k v]] (pred k)) map)))


(defn board-graph
  [size]
  (let [g (reduce (fn [map position] (assoc map
                                            position
                                            (neighbors size position)))
                  {}
                  (positions size))]
    (merge g
           (reduce-kv #(assoc %1 %2 (conj %3 :TOP)) {}
                      (filter-keys (fn [[x y]] (= y 0)) g))
           (reduce-kv #(assoc %1 %2 (conj %3 :BOTTOM)) {}
                      (filter-keys (fn [[x y]] (= y (dec size))) g))
           (reduce-kv #(assoc %1 %2 (conj %3 :LEFT)) {}
                      (filter-keys (fn [[x y]] (= x 0)) g))
           (reduce-kv #(assoc %1 %2 (conj %3 :RIGHT)) {}
                      (filter-keys (fn [[x y]] (= x (dec size))) g)))))

I basically want to build my map, go over it again and for certain keys update the value associated there depending on what the key is. 我基本上想构建我的地图,再次遍历它,并且对于某些键,根据键是什么,在那里更新关联的值。 I can't find a nice way to do this without resorting to state! 不求助于状态,我找不到一个很好的方法! Is there a more idiomatic way of doing this? 有没有更惯用的方法呢?

You can just add a few more expressions to your threaded expression where you build the returned vector. 您可以在创建返回的矢量的线程表达式中添加一些表达式。 To make this read more nicely I switched it from the thread first macro -> to the "thread as" macro as-> which binds the symbol (in this case) c to the value being threaded at each step so I can use it in an a conditional expression on some of the stages: 为了使此内容更好看,我将其从线程第一个宏->切换为“线程为”宏as-> ,该宏将符号c(在这种情况下)绑定到每个步骤中要线程化的值,因此可以在在某些阶段的条件表达式:

(defn neighbors [size [x y]]
  (filter (fn [[x y]]
            (and (>= x 0) (< x size) (>= y 0) (< y size)))
          (as-> [] c
              (conj c [(inc x) y])
              (conj c [(dec x) y])
              (conj c [x (inc y)])
              (conj c [x (dec y)])
              (conj c [(inc x) (inc y)])
              (conj c [(dec x) (dec x)])
              (if (zero? x)  (conj c :TOP) c)
              (if (= size x) (conj c :BOTTOM) c)
              (if (zero? y)  (conj c :LEFT) c)
              (if (= size y) (conj c :RIGHT) c))))

each conditional expression either returns an updated version, or passes it through unchanged if the condition was not met. 每个条件表达式要么返回更新的版本,要么在不满足条件的情况下通过更新版本。

This works for me, but it isn't very close to what you wrote originally. 这对我有用,但是与您最初编写的内容不太接近。

(defn positions [size]
  (for [x (range 0 size) y (range 0 size)] [x y]))

(defn neighbors [n [x y]]
  (let [left (if (zero? x)
               :LEFT
               [(dec x) y])
        right (if (= x (dec n))
                :RIGHT
                [(inc x) y])
        up (if (zero? y)
             :TOP
             [x (dec y)])
        down (if (= y (dec n))
               :BOTTOM
               [x (inc y)])]
    (list left right up down)))

(defn board-graph [n]
  (let [keys (positions n)
        vals (map (partial neighbors n) keys)]
    (zipmap keys vals)))

then 然后

(clojure-scratch.core/board-graph 2)
=>
{[1 1] ([0 1] :RIGHT [1 0] :BOTTOM),
 [1 0] ([0 0] :RIGHT :TOP [1 1]),
 [0 1] (:LEFT [1 1] [0 0] :BOTTOM),
 [0 0] (:LEFT [1 0] :TOP [0 1])}

EDIT: As Arthur noted, this doesn't handle diagonals, if you want that. 编辑:正如亚瑟(Arthur)指出的那样,如果您愿意的话,这不会处理对角线。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM