轨道
/
Clojure
Clojure
/
练习
/
纸牌游戏
纸牌游戏

纸牌游戏

学习练习

简介

在 Clojure 中,数组是一种集合,就像其他语言里的数组一样。和 Lisp 家族的其他语言类似,Clojure 用圆括号来表示数组。

创建 Clojure 数组有两种方式。可以用 list 函数创建数组,也可以用 quote 引用一个字面量数组。

数组的特殊之处在于,Clojure 会把它们当作_调用_来处理。调用必须以一个_运算符_开头,通常是一个函数。数组中剩下的元素则被视为_操作数_,也就是说,它们会成为函数的实参。

Clojure 对数组的这种特殊处理,正是我们不能直接创建数组字面量的原因。用 quote 或其简写 ' 引用一个数组,表示这个数组不应被求值。

与一些现代语言不同,Clojure 的数组是_异构的_,也就是说,它们内部可以包含多种类型的元素,例如 '(2 "a" "b" 3)。 与其他 Lisp 不同,Clojure 中的空数组是真值,并不等同于 nil 或 false。

说明

Elyse 非常期待在即将到来的拉斯维加斯之旅中玩几局扑克(以及其他纸牌游戏)。她是个不折不扣的“自我追踪”爱好者,想写几个小函数来帮自己完成各种记录任务,并请你帮她一起理清思路。

1. 记录扑克轮次

Elyse 特别喜欢扑克,她想记录自己玩了多少轮,以及_具体是哪几轮_。每一轮都有自己的编号,每张牌桌都会显示当前正在进行的轮次编号。Elyse 选了一张牌桌坐下,开始玩第一轮。她打算玩三轮。

实现一个函数 rounds,它接收当前轮次编号,并返回一个 list,其中包含该轮以及接下来要玩的两轮:

(rounds 27)
;;=> (27 28 29)

2. 把所有轮次放在一起

Elyse 在第一张牌桌玩了几轮,然后休息了一下,又在第二张牌桌玩了几轮……结果每张牌桌都得到了一份不同的列表!她想把这两份列表合并起来,这样就能在同一处记录所有的扑克轮次。

实现一个函数 concat-rounds,它接收两个列表,并返回一个 list,其中先包含第一个 list 中的所有轮次,再包含第二个 list 中的所有轮次:

(concat-rounds '(27 28 29) '(35 36))
;;=> (27 28 29 35 36)

3. 查找之前的轮次

在聊到之前的一些扑克轮次时,另一位玩家说其中两轮玩得格外相似。Elyse 不确定自己有没有玩过那几轮。

实现一个函数 contains-round?,它接收两个参数:一个已玩轮次的列表和一个轮次编号。如果该轮次在已玩轮次的列表中,函数返回 true,否则返回 false:

(contains-round? '(27 28 29 35 36) 29)
;;=> true

(contains-round? '(27 28 29 35 36) 30)
;;=> false

4. 计算牌面值的平均值

Elyse 想试试一种叫 Black Joe 的新游戏。它和 21 点类似,都是让手里的牌加起来等于一个目标值。不过在 Black Joe 里,目标是让牌面值的_平均值_等于 7。平均值可以先把所有牌面值相加,再用这个和除以手中牌的数量求得。

实现一个函数 card-average,它返回一手 Black Joe 牌的牌面平均值。

(card-average '(5 6 7))
;;=> 6.0

5. 其他的平均值算法

在 Black Joe 里,速度很重要。Elyse 想试着找到一种更快求出平均值的方法。

她想到了两种得到_近似平均值_的方法:

  • 取手中_第一张_和_最后一张_牌面值的平均值。
  • 使用手中牌的中位数(中间那张牌)。

实现函数 approx-average?,它接收 hand,一个包含手中牌面值的列表。

如果上面两种策略中有_一个_or_两个_得到的结果与_实际平均值_相等,就返回 true。

注意:为了更容易找到中位数,所有牌的数量都是奇数。

(approx-average? '(1 2 3))
;;=> true

(approx-average? '(2 3 4 8 8))
;;=> true

(approx-average? '(1 2 3 5 9))
;;=> false

6. 更多求平均值的方法

Elyse 对平均值实验的结果很感兴趣,她想知道取_偶数_位置牌面值的平均值,与取_奇数_位置牌面值的平均值,是否会得到相同的结果。该再写一个测试函数了!

实现一个函数 average-even-odd?,它返回一个布尔值,表示偶数下标牌面值的平均值是否与奇数下标牌面值的平均值相同。

(average-even-odd? '(1 2 3))
;;=> true

(average-even-odd? '(1 2 3 4))
;;=> false

7. 奖励轮规则

在 Black Joe 里,每第 11 手牌都是奖励牌,附带一条奖励规则:如果你抽到的最后一张牌是 Jack,就把它的值加倍。

实现一个函数 maybe-double-last,它接收一手牌,并检查最后一张牌是不是 Jack(11)。如果最后一张牌是 Jack(11),就先把它的值加倍,再返回这手牌。

(maybe-double-last '(5 9 11))
;;=> '(5 9 22)

(maybe-double-last '(5 9 10))
;;=> '(5 9 10)
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Clojure Exercism

准备好开始 纸牌游戏 了吗?

注册 Exercism,借助 12 个概念105 个练习 和真人导师指导,学习并掌握 Clojure,全部免费。