I want to represent a graph of 2D positions + neighbors in Clojure for a board game. 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, .e.g :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?