用Clojure上手洗牌-几乎
我正试图在Clojure中实现一个应用程序,作为一个学习练习 所以我有这个密码用Clojure上手洗牌-几乎,clojure,functional-programming,lisp,shuffle,Clojure,Functional Programming,Lisp,Shuffle,我正试图在Clojure中实现一个应用程序,作为一个学习练习 所以我有这个密码 (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 (m
(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)
另外,作为旁注,这种缩进/组织此函数的方式非常难看,有没有更明显的方式?此函数创建列表列表,转换每个列表,并将它们重新分类在一起。问题在于,它每次都从同一个对象中提取,并附加到一个固定值。从本质上讲,它每次都运行相同的操作,因此它重复输出,而不进行列表。如果您以不同的方式分解问题,并将随机大小块的创建从将它们串在一起的过程中分离出来,那么就更容易看到如何使其正确工作 拆分序列的一些方法:
(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)))
(reduce into (random-partitions [1 2 3 4 5 6 7 8 9 10]))
(10 9 8 7 6 5 4 3 1 2)
使分区长度小于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)))
然后可以将其还原为单个序列:
(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)))
(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)
回答缩进问题时,可以重构函数。例如,从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)))))
您可以将这些原则应用于固定代码
许多缩进问题可以通过简单地分解复杂表达式来解决。一般来说,它也有助于可读性。组织函数的更好方法是将洗牌动作与随机选择的驱动它的分割点分开。然后我们可以用可预测的拆分器测试洗牌器 洗牌动作可以表示为
(defn shuffle [deck splitter]
(if (empty? deck)
()
(let [[taken left] (split-at (splitter (count deck)) deck)]
(concat (shuffle left splitter) taken))))
在哪里
是要洗牌的序列deck
是一个函数,它根据其 尺寸李>拆分器
splitter
s的shuffle
:
=> (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)
它起作用了
现在让我们看看选择分割点的方式。我们可以举例说明您选择的\u rand\u天花板
:
=> (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)))
您能否包含运行此操作所需的定义sample@ArthurUlfeldt更新了完整的功能谢谢你添加了这个,它使代码更加清晰。这种语言对我来说是如此有趣和独特,如此简洁。鼓励您真正聪明地解决问题。这会让将内容拆分为可重复使用的独立块感觉更自然:)因此,我的
rand_-ceiling
应该确保没有大于此值的项目块。做(=0(rand int rand_ceiling))
并不会真正影响到。。。基本上,我想说的是,我希望每个区块随机选择N个项目,但不要超过rand\u上限
。。。想法?上述函数中的(defn f
对于并发使用是不安全的,如果其中两个正在运行,它们将争夺当前的f值(如果在不同的线程中,它们将失败)。在构建函数时,使用letfn
或let
来安全关闭卡的值。您只需将defn
更改为fn
,然后将其向上移动一行到let中即可。