轨道
/
R
R
/
练习
/
二十一点
二十一点

二十一点

学习练习

简介

switch()函数可以简洁地替代一长串 if ... else if 判断。 用于 switch 的变量通常是一个字符串,这种情况下,选择器里可以省略引号。

star <- function(type) {
  switch(type,
    M = , # will "fall through" if no value given
    K = "red dwarf",
    G = "Earth-like",
    "bigger star" # only correct for O,B,A,F
  )
}

> star("M")
[1] "red dwarf"

注意,只有像上面例子中的 M 那样把值留空时,选项才会向下穿透。 不像其他一些语言那样,这里不需要写 break 语句。

对 character 类型进行 switch 时,最后一个值就是默认值,就像这里一样。 而对于整数类型,则无法指定默认值。

dplyr中的函数

base R 中的switch相当有限:它只能对单个输入做精确匹配。 在 1993 年 R 首次发布时,这看起来很合理,但按现代的使用需求,它还有待改进。

Note

正如Conditionals概念中提到的,tidyverse系列包的设计宗旨是补充(有时是替代)base R 的功能,同时不影响向下兼容。

只需在代码顶部加上library(dplyr)(用于单个包)或library(tidyverse)(用于整个系列),就能把dplyr包引入作用域。

dplyr库提供了两个与switch相关的额外函数。

recode_values和replace_values函数

这两个相关的函数可以把旧值以类似 switch 的、向量化的方式映射为新值。

它们的主要区别在于,recode_values()会创建一个全新的向量,而replace_values()允许对已有向量做部分更新。

匹配仍然是精确匹配,不过:

  • 左侧的选项可以是向量,只要其中任一元素匹配,输入就算匹配。
  • 左侧的选项和右侧的结果用波浪号~连接,而不是=。
  • 所有情况下都可以指定默认值。
library(dplyr)

x <- c("a", "b", "a", "d", "b", NA, "c", "e", "z")
recode_values(
  x,
  "a" ~ 1,
  "b" ~ 2,
  "c" ~ 3,
  c("d", "e") ~ 4, # either "d' or "e" will match
  NA ~ 0,          # matches missing values
  default = 100   # note the different syntax for the default
)
#> [1]   1   2   1   4   2   0   3   4 100

case_when函数

case_when把case_match的语法又推进了一步:它允许在~左侧写任意逻辑表达式。

输入向量(下面例子中的x)不是作为实参传入的,它只需要已经定义好。

x <- 1:10
case_when(
  x < 3 ~ "low",
  between(x, 3, 5) ~ "mid",
  between(x, 6, 8) ~ "high",
  .default = "what?"
)
#>  [1] "low"   "low"   "mid"   "mid"   "mid"   "high"  "high"  "high"  "what?" "what?"

between()函数也是dplyr的一部分。

说明

在这个练习中,我们要模拟一局21 点游戏的第一回合。

你会拿到两张牌,并且可以看到庄家的明牌。所有牌都用字符串表示,例如 “ace”、“king”、“three”、“two” 等。每张牌的值如下:

牌 值 牌 值
ace 11 eight 8
two 2 nine 9
three 3 ten 10
four 4 jack 10
five 5 queen 10
six 6 king 10
seven 7 其他 0

注意:通常 ace 的值可以是 1 或 11,但为了简单起见,我们假设它只能取 11。

根据你的两张牌和庄家的牌,游戏的第一回合有一套策略,你有以下几种选择:

  • 停牌(S)
  • 要牌(H)
  • 分牌(P)
  • 自动获胜(W)

这套策略虽然还不是最优的,但你要按照你的朋友 Alex 一直在制定的策略来做,具体如下:

  • 如果你有一对 ace,就必须分牌。
  • 如果你拿到了 21 点(两张牌的值加起来等于 21),而庄家的牌不是 ace、人头牌或 ten,那么你就自动获胜。如果庄家的牌是其中任意一种,你就必须停牌,等庄家亮出另一张牌。
  • 如果你的牌加起来的值在 [17, 20] 区间内,就总是停牌。
  • 如果你的牌加起来的值在 [12, 16] 区间内,就总是停牌,除非庄家的牌是 7 或更大,这时你就总是要牌。
  • 如果你的牌加起来的值是 11 或更低,就总是要牌。

1. 计算任意一张牌的值。

实现一个函数 parse_card,用来计算一张牌的值:

parse_card("ace")
# => 11

2. 实现第一回合的决策逻辑。

写一个函数 first_turn,实现上面描述的决策逻辑:

first_turn(card1, card2, dealer_card)

下面是几个预期结果的例子:

first_turn("ace", "ace", "jack") == "P"
first_turn("ace", "king", "ace") == "S"
first_turn("five", "queen", "ace") == "H"
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
R Exercism

准备好开始 二十一点 了吗?

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