Как реализовать многопоточность - PullRequest
0 голосов
/ 03 мая 2018

Правила игры

Рассмотрим простую игру для двух игроков, в которую играют следующим образом: в ряд выложено четное количество монет. По очереди каждый игрок убирает монету на одном из концов ряда. Цель состоит в том, чтобы иметь наибольшее значение в монетах, когда все монеты были взяты.

Игрок один находит сумму всех монет с четным номером и всех монет с нечетным номером. Если сумма монет с нечетным номером выше, игрок один берет самую левую монету; в противном случае он выбирает самое правое.

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

Проблема

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

Код

(ns game.core
  (:gen-class))

(defn vector-from-string [s]
  (drop 1 (map read-string (clojure.string/split (clojure.string/trim-newline s) #" "))))

(defn string-from-file [f]
  (slurp f))

(defn sum-of-evens [v]
  (def evens (vector))
  (loop [v v, index 1]
    (when (seq v)
      (if (even? index)
        (def evens (conj evens (first v))))
      (recur (rest v) (inc index))))
  (reduce + evens))

(defn sum-of-odds [v]
  (def odds (vector))
  (loop [v v, index 1]
    (when (seq v)
      (if (odd? index)
        (def odds (conj odds (first v))))
      (recur (rest v) (inc index))))
  (reduce + odds))

(defn player-two [v p1score p2score]
  (if (not (empty? v))
    (if (> (max (sum-of-odds (drop 1 v)) (sum-of-evens (drop 1 v))) (max (sum-of-odds (drop-last v)) (sum-of-evens (drop-last v))))
      (player-one (drop-last v) p1score (+ p2score(last v)))
      (player-one (drop 1 v) p1score (+ p2score (first v))))
    (println "p1score" p1score "p2score" p2score)))

(defn player-one [v p1score p2score]
  (if (not (empty? v))
    (if (> (sum-of-odds v) (sum-of-evens v))
      (player-two (drop 1 v) (+ p1score (first v)) p2score)
      (player-two (drop-last v) (+ p1score (last v)) p2score))
    (println "p1score" p1score "p2score" p2score)))

(defn -main [& args]
  (let [v (vector-from-string (string-from-file "numbers.txt")) ]
    (player-one v 0 0)))

Итак, -main сначала запускает функцию player-one, а player-one вызывает player-two, и они оба продолжаются до конца программы. Я хотел бы как-то реализовать многопоточность, чтобы ускорить выполнение этой игры с большим количеством начальных монет.

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

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

sum-of-evens использует def внутри функции, что почти всегда является ошибкой. Может показаться, что это дает желаемый эффект, но это неправильный способ его достижения. def s обычно используются для значений уровня пространства имен (на том же уровне, что и ваша функция defn s). Мы можем изменить рефакторинг sum-of-evens, чтобы не полагаться на непреднамеренное побочное действие через def:

(defn sum-of-evens [v]
  (loop [v v
         index 1
         evens []]
    (if (seq v)
      (recur (rest v)
             (inc index)
             (if (even? index) ;; add a binding to loop, not a def
               (conj evens (first v))
               evens))         ;; pass unchanged value when necessary
      (reduce + evens))))

Но мы можем еще больше упростить эту функцию с помощью keep-indexed:

(defn sum-of-evens [coll]
  (->> coll
       (keep-indexed (fn [i v] (when (even? (inc i))
                                 v)))
       (apply +)))

И когда мы делаем то же самое для sum-of-odds, мы видим, что функции почти идентичны, за исключением условия, которое они используют: odd? против even?. Мы можем сделать другую функцию, которая принимает предикатную функцию:

(defn sum-by-index-pred [f coll]
  (->> coll
       (keep-indexed (fn [i v] (when (f i) v)))
       (apply +)))
;; using partial application and function composition
(def sum-of-evens (partial sum-by-index-pred (comp even? inc)))
(def sum-of-odds (partial sum-by-index-pred (comp odd? inc)))

Глядя на реализацию player-one и player-two, они кажутся взаимно рекурсивными. Я не понимаю, как вы могли бы распараллелить это, чтобы сделать это быстрее, потому что каждый ход зависит от результата предыдущего хода; нечего распараллеливать.

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

(loop [scores (array-map :player-1 0 :player-2 0)
       turns (cycle (keys scores))
       vs (shuffle (range 100))]
  (if (seq vs)
    (let [higher-odds? (> (sum-of-odds vs) (sum-of-evens vs))
          scores (if higher-odds?
                   (update scores (first turns) + (first vs))
                   (update scores (first turns) + (last vs)))
          remain (if higher-odds?
                   (rest vs)
                   (butlast vs))]
      (recur scores (rest turns) remain))
    (prn scores)))
;; {:player-1 2624, :player-2 2326}

Я не уверен, сохраняет ли это исходную игровую логику, но она должна быть близкой, и она обобщает ее для более чем двух игроков. Попробуйте добавить :player-3 0 к началу scores.

0 голосов
/ 03 мая 2018

Ваш код в настоящее время очень недиоматичен.

Несколько замечаний, которые, надеюсь, помогут вам выбрать правильное направление:

A def внутри defn (или def) (почти) всегда неверен. Вы думаете здесь с точки зрения назначения переменных и изменяемых переменных. Это не так, как работает Clojure. Вместо этого используйте переменные в вашем recur, если это абсолютно необходимо, используйте локальный атом (также почти всегда неправильно, но реже неправильно, чем def внутри defn).

Ваши циклы излишне сложны. Вы хотите суммировать элементы по четным или нечетным индексам? Используйте комбинацию reduce, take-nth и rest:

(take-nth 2 [1 2 3])
;=> (1 3)
(take-nth 2 (rest [1 2 3 4]))
;=> (2 4)

Все выглядит так, будто вы собираете это снова и снова, а затем запускаете JVM с ним. Я прав? Предпочтительным способом является работа в REPL. Как получить к нему доступ, зависит от того, какую среду редактирования вы используете. Есть много удобных для начинающих РЕПЛ. Горилла REPL является одним из примеров.

После того как ваш код и рабочий процесс разработки будут в лучшей форме, вы, возможно, захотите изучить такие функции, как pmap и future, для быстрого доступа к многопоточности. Более сложные вещи включают библиотеку под названием core.async, но это, вероятно, не идеальный маршрут для начинающих. Вы также можете вернуться к взаимодействию Java для создания своих потоков. Опять же, кое-что, хотя это и не очень сложно сделать, но требует немного опыта с Clojure.

Надеюсь, это поможет, даже если это не прямой ответ на ваш вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...