2

我正在尝试在 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)

另外,作为一个旁注,这感觉像是一种非常丑陋的缩进/组织这个函数的方式,有没有更明显的方式?

4

3 回答 3

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)))

保持分区小于四

(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)

如果您将参数反转为 into 它看起来像一个更好的洗牌

 (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)
于 2012-11-29T22:31:09.023 回答
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)))))

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

很多缩进问题可以通过简单地分解出复杂的表达式来解决。它还有助于总体上的可读性。

于 2012-11-30T13:50:16.347 回答
0

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

洗牌动作可以表示为

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

在哪里

  • deck是要洗牌的序列
  • splitterdeck是一个在给定大小的情况下选择拆分位置的函数。

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

=> (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)))
于 2014-02-03T18:37:52.373 回答