В Clojure "Игра жизни"


Я новичок в Clojure и я реализовал игру жизни , чтобы узнать, как я должен код вещи в Clojure.

(def state (ref [
    [false false false false false]
    [false false true  false false]
    [false false true  false false]
    [false false true  false false]
    [false false false false false]]))

(defn get-state [state x y]
  (nth (nth state y) x))

(defn count-true-neighbours [state x y]
  (let [size (count state)]
    (->>
      (for [p (range (- x 1) (+ x 2))
            q (range (- y 1) (+ y 2))
            :when (not (and (= x p) (= y q)))]
        [p q])
      (map (fn [coord]
        (let [[p q] coord]
          (if (or (< p 0) (>= p size) (< q 0) (>= q size))
            false
            (get-state state p q)))))
      (filter true?)
      (count))))

(defn next-state-of [state x y]
  (let [alive (get-state state x y)
        alive-neighbours (count-true-neighbours state x y)]
    (cond
      (and (not alive) (= alive-neighbours 3)) true
      (and alive (or (= alive-neighbours 2) (= alive-neighbours 3))) true
      :else false)))

(defn next-state [state]
  (let [size (count state)]
    (mapv
      (fn [row]
        (mapv
          #(next-state-of state %1 row)
          (range 0 size)))
      (range 0 size))))

(defn pretty-print [state]
  (let [size (count state)]
    (loop [i 0]
      (if (< i size)
        (do
          (println
            (->>
              (nth state i)
              (mapv #(if %1 "■ " "□ "))
              (reduce str)))
          (recur (inc i)))))))

(defn tick
  ([times] (tick 0 times))
  ([i times]
    (if (< i times)
      (do
        (dosync
          (println (str i ":"))
          (pretty-print @state)
          (ref-set state (next-state @state)))
        (recur (inc i) times))
      nil)))

(defn -main [& args]
  (tick 5))

Я ref чтобы узнать, как управлять государством, хотя это кажется излишним для этой крошечной программы.

Как вы видите мой код ужасен, но я не мог улучшить это сам.

Я уже проверил этот ответ, но я думаю, что я сделал это совершенно по-другому ведут и нужен другой совет.



Комментарии
1 ответ

Ваш код на самом деле не страшный, но есть несколько мест, где он может быть улучшен.


Во-первых, ref. Это совершенно ненужный в данном случае. Что я бы изменил, чтобы сохранить все неизменным:

Измените ваше заявление в верхней части просто:

(def initial-state [[false false false false false]
[false false true false false]
[false false true false false]
[false false true false false]
[false false false false false]])

Это сейчас определяет, как имя предполагает, начальное состояние игры. Этого больше не будет использоваться для хранения текущего состояния.

Теперь, вы можете изменить tick чтобы избавиться от необходимости ref-set:

(defn tick [state times]
(loop [i 0 ; Moved the counter into a loop
acc-state state] ; Keeping track of the running state
(when (< i times)
(println (str i ":"))
(pretty-print acc-state)

; Recur into the loop instead of the function itself
(recur (inc i) (next-state acc-state)))))

(defn -main [& args]
(tick initial-state 5))

Обратите внимание, как я сделал рабочее состояние аккумулятора loop. Я думаю, что вся конструкция должна быть переосмыслена. Действительно, Вы могли бы просто сделать последовательных состояний с помощью iterateи take столько, сколько вы хотите. Допустим, вы хотите сделать первых пяти государств после initial-state:

(take 5 (iterate next-state initial-state))

Теперь, весь tick функция в принципе может быть написано как:

; Obviously, change 5 to however many states you want to produce
(doseq [state (take 5 (iterate next-state initial-state))]
(pretty-print state))


Я бы поменял pretty-print к format стиль функция, которая возвращает строку, вместо того, чтобы печатать напрямую. Таким образом, абонент может использовать свой украшали, но решить, как и когда они хотят, чтобы отобразить его.


Ваш count-true-neighbors функция огромна. Я бы разбить его на несколько неброских деталей:


  • У вас есть часть, которая создает список соседей, чтобы проверить

  • У вас есть часть, которая делает проверки границ

Я бы изменить его на что-то более похожее:

(defn neighbors-of [x y]
(for [p (range (- x 1) (+ x 2))
q (range (- y 1) (+ y 2))
:when (not (and (= x p) (= y q)))]
[p q]))

(defn inbounds? [x y size]
(and (<= 0 x (dec size)) ; Comparison operators can be chained!
(<= 0 y (dec size))))

(defn count-true-neighbours [state x y]
(let [size (count state)]
(->> (neighbors-of x y)
(map (fn [[p q]] ; You can deconstruct here directly
(when (inbounds? p q size)
(get-state state p q))))
(filter true?)
(count))))

Ваш mapфункция ping может быть изменен на:

(map (fn [[p q]]
(and (inbounds? p q size)
(get-state state p q))))

Хотя выгода здесь является субъективным.

Вещи, чтобы отметить:


  • <= и другие операторы сравнения можно использовать для проверки более чем двумя аргументами! Я использования, что в inbounds? чтобы выровнять его немного.

  • Я уменьшил свои границы проверить на карте до (when (inbounds? p q size) ...). when возвращает nil (falsey) если условие не выполняется, поэтому вам не нужно явно возвращать false.


(or (= alive-neighbours 2) (= alive-neighbours 3)))

Также можно записать так:

(#{2 3} alive-neighbors)

Это вопрос стиля, который вы предпочитаете. Я предпочитаю последний лично.


(nth (nth state y) x))

Также можно записать так:

(get-in state [y x])    


Есть несколько вещей, чтобы отметить в pretty-print:

(reduce str strings)

Не ошибаешься, но более идиоматические способ написания это будет:

(apply str strings)

С:

(mapv #(if %1 "■ " "□ "))

Добавить явные пробелы после отображения символов. Он может быть чистым, чтобы использовать join чтобы добавить пространства. Это ограничивает повторения, и избавляется от пробел. Это также позволяет просто использовать символьные литералы непосредственно.

И когда вы используете нитку макросы (->>), вы ставите выражения нанизанных на следующей строке. Лично я предпочитаю ставить резьбовые выражение в той же строке, как ->>. Это делает его понятнее, что с резьбой.

Принимая, что все вместе, я пишу, что все как:

(->> (nth state i)
(mapv #(if %1 \■ \□))
(s/join " ")
(apply str)))

Где s это псевдоним clojure.string.

2
ответ дан 17 марта 2018 в 03:03 Источник Поделиться