我正在尝试在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)
另外,作为旁注,这对于缩进/组织此功能感觉是一种非常难看的方式,是否有更明显的方法?
答案 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)))