关于clojure:如何实现多线程

How to implement multithreading

游戏规则

考虑一个简单的2人游戏,其玩法如下:连续排列偶数个硬币。轮流,每位玩家从该行两端的硬币中取出硬币。当所有硬币都被取走时,目标是使硬币中的价值最高。

玩家一发现所有偶数硬币和所有奇数硬币的总和。如果奇数硬币的总和更高,则玩家1拿走最左边的硬币;否则,玩家1拿走最左边的硬币。否则,他将获得最右边的权利。

玩家二有一个选择,可以选择奇数个硬币。因此,他尝试从两端拿硬币,以查看哪个选项会使玩家1的状况更糟。

问题

我基本上想对该程序实施多头处理。我对Clojure还是很陌生,而且我在网上真的找不到关于多线程的任何适用于我的程序的好材料。

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
(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,它们都继续进行直到程序结束。我想以某种方式实现多线程,从而以更高的起始金币来加快游戏的执行速度。


您的代码目前非常简单。

希望能帮助您朝正确方向发展的几点评论:

defn(或def)内的def(几乎)总是错误的。您正在考虑变量分配和可变变量。这不是Clojure的工作方式。如果绝对必须,请在您的递归中使用变量,而请使用局部原子(也几乎总是错误的,但与defn中的def一样不常见)。

您的循环不必要地复杂。您想对偶数或奇数索引的元素求和?组合使用reducetake-nthrest

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

整个过程看起来像是一次又一次地编译它,然后使用它运行JVM。我对吗?首选方式是在REPL工作。如何访问它,取决于您使用的编辑环境。有很多初学者友好的REPL。大猩猩REPL就是一个例子。

一旦使代码和开发工作流状态变得更好,您可能希望探索pmapfuture之类的功能,以便轻松访问多线程。更高级的内容涉及一个名为core.async的库,但这对于初学者而言可能不是理想的选择。您还可以使用Java互操作来创建线程。同样,尽管并非难事,但需要一些Clojure经验。

希望对您有所帮助,即使这不是您问题的直接答案。


首先,让我们看一下示例中的一些问题,这些问题在并行化此代码之前需要解决。

sum-of-evens在函数内部使用def,这几乎总是一个错误。这似乎具有您想要的效果,但这不是实现它的正确方法。 def通常用于命名空间级别(与功能defn处于同一级别)。我们可以通过def重构sum-of-evens使其不依赖无意的副作用行为:

1
2
3
4
5
6
7
8
9
10
11
(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进一步简化此功能:

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

当我们对sum-of-odds进行相同操作时,我们可以看到这些功能几乎完全相同,除了使用的条件不同:odd?even?。我们可以创建另一个带有谓词函数的函数:

1
2
3
4
5
6
7
(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-oneplayer-two的实现,它们似乎是相互递归的。我看不到如何并行处理以使其更快,因为每个转弯都取决于前一转弯的结果。没有什么可以并行化了。

我建议将其重构,以便将游戏规则和状态计算在一个位置,而不是相互递归的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(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中。