用Clojure随意洗牌 - 几乎

时间:2012-11-29 21:08:34

标签: clojure functional-programming lisp shuffle

我正在尝试在Clojure中实现Overhand Shuffle作为一个学习练习

所以我有这个代码......

(defn overhand [cards]
    (let [ card_count (count cards)
          _new_cards '()
         _rand_ceiling (if (> card_count 4) (int (* 0.2 card_count)) 1)]
      (take card_count
            (reduce into (mapcat
                           (fn [c]
                             (-> (inc (rand-int _rand_ceiling))
                                 (take cards)
                                 (cons _new_cards)))
                           cards)))))

它非常接近于我想做的事情,但它反复从前面拿出第一张(随机)N张牌,但我希望它能够在列表中前进......

呼叫

(overhand [1 2 3 4 5 6 7 8 9])

而不是以

结尾
(1 2 3 1 2 1 2 3 4)

我想以

结束
(7 8 9 5 6 1 2 3 4)

另外,作为旁注,这对于缩进/组织此功能感觉是一种非常难看的方式,是否有更明显的方法?

3 个答案:

答案 0 :(得分:3)

这个函数正在创建一个列表列表,转换每个列表,并将它们重新组合在一起。问题是它每次都从同一个东西中拉出并附加到固定值。本质上它每次都在运行相同的操作,所以它重复输出而没有列出的进展。如果你以不同方式解决问题,并将随机大小的块的创建从将它们串在一起分开,那么就可以更容易地看到如何使其正常工作。

分裂序列的一些方法:

(defn random-partitions [cards]
  (let [card_count (count cards)
        rand_ceiling (if (> card_count 4) (inc (int (* 0.2 card_count))) 1)]
   (partition-by (ƒ [_](= 0 (rand-int rand_ceiling))) cards)))

保持分区小于4的长度

(defn random-partitions [cards]
  (let [[h t] (split-at (inc (rand-int 4)) cards)]
    (when (not-empty h) (lazy-seq (cons h (random-partition t))))))

或者将分区保留为原始问题中的大小

(defn random-partitions [cards]
  (let [card_count (count cards)
        rand_ceiling (if (> card_count 4) (inc (int (* 0.2 card_count))) 1)
        [h t] (split-at (inc (rand-int rand_ceiling)) cards)]
    (when (not-empty h) (lazy-seq (cons h (random-partition t))))))

(random-partitions [1 2 3 4 5 6 7 8 9 10])
((1 2 3 4) (5) (6 7 8 9) (10))

这也可以在不直接使用lazy-seq的情况下编写:

(defn random-partitions [cards]
  (->> [[] cards]
       (iterate
        (ƒ [[h t]]
          (split-at (inc (rand-int 4)) t)))
       rest ;iterate returns its input as the first argument, drop it.
       (map first)
       (take-while not-empty)))

然后可以减少回单个序列:

(reduce  into (random-partitions [1 2 3 4 5 6 7 8 9 10]))
(10 9 8 7 6 5 4 3 1 2)

如果你将参数反转到它看起来好像是一个更好的随机播放

 (reduce #(into %2 %1) (random-partitions [1 2 3 4 5 6 7 8 9 10]))
(8 7 1 2 3 4 5 6 9 10)

答案 1 :(得分:0)

回答你的缩进问题,你可以重构你的功能。例如,从mapcat中拉出lambda表达式,定义它,然后在调用mapcat时使用它的名字。你不仅可以帮助缩进,而且你的mapcat会更清晰。

例如,这是您的原始程序,重构。请注意,您的程序问题尚未得到纠正,我只是展示了一些改进布局的示例:

(defn overhand [cards]
    (let [ card_count (count cards)
          _new_cards '()
         _rand_ceiling (if (> card_count 4) (int (* 0.2 card_count)) 1)]

        (defn f [c]
            (-> (inc (rand-int _rand_ceiling))
                (take cards)
                (cons _new_cards)))

        (take card_count (reduce into (mapcat f cards)))))

您可以将这些原则应用于您的固定代码。

通过简单地分解复杂的表达式,可以解决大量的缩进问题。它还有助于提高可读性。

答案 2 :(得分:0)

组织函数的更好方法是将混洗动作与驱动它的随机选择分离点分开。然后我们可以用可预测的分离器测试洗牌器。

改组动作可以表示为

(defn shuffle [deck splitter]
  (if (empty? deck)
    ()
    (let [[taken left] (split-at (splitter (count deck)) deck)]
      (concat (shuffle left splitter) taken))))

,其中

  • deck是要改组的序列
  • splitter是一个选择分割deck的位置的函数 尺寸。

我们可以针对一些简单的shuffle s测试splitter

=> (shuffle (range 10) (constantly 3))
(9 6 7 8 3 4 5 0 1 2)
=> (shuffle (range 10) (constantly 2))
(8 9 6 7 4 5 2 3 0 1)
=> (shuffle (range 10) (constantly 1))
(9 8 7 6 5 4 3 2 1 0)

有效。

现在让我们来看看你选择分裂点的方式。我们可以说明您对_rand_ceiling的选择:

=> (map
     (fn [card_count] (if (> card_count 4) (int (* 0.2 card_count)) 1))
     (range 20))
(1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3)

这意味着您将从不到十张的任何套牌中只拿一张或两张牌。顺便说一下,表达函数的一种更简单的方法是

(fn [card_count] (max (quot card_count 5) 1))

因此我们可以将您的拆分器功能表达为

(fn [card_count] (inc (rand-int (max (quot card_count 5) 1))))

所以我们想要的洗牌是

(defn overhand [deck]
  (let [splitter (fn [card_count] (inc (rand-int (max (quot card_count 5) 1))))]
    (shuffle deck splitter)))