8.2 流程控制
我们一般不满足于仅仅逐行地执行代码,而是希望更好地控制它们的执行流程。这就意味着只有在某些条件满足后你会才执行一些代码。
8.2.1 if
和else
最简单的流程控制逻辑是使用if
。if
接受一个逻辑值(更准确地说是一个长度为1的逻辑向量)作为参数,且当该值为TRUE
时才会执行下一条语句:
if(TRUE) message("It was true!")
## It was true!
if(FALSE) message("It wasn't true!")
if
的条件中不允许缺失值,这样做会抛出一个错误:
if(NA) message("Who knows if it was true?")
## Error: missing value where TRUE/FALSE needed
如果你的条件中可能会出现缺失值,先用is.na
来测试它:
if(is.na(NA)) message("The value is missing!")
## The value is missing!
当然,大部分时候你都不会直接传入TRUE
或FALSE
值,而是传递一个变量或表达式——因为如果知道该语句将被提前执行,就不需要if
语句了。在下例中,runif(1)
将在0和1之间生成一个均匀分布的随机数。如果该值超过0.5
,则显示以下消息:
if(runif(1) > 0.5) message("This message appears with a 50% chance.")
如果你想有条件地执行多个语句,就把它们括在大括号中:
x <- 3
if(x > 2)
{
y <- 2 * x
z <- 3 * y
}
为了使代码更清晰,一些编程风格指南建议:即使条件执行语句只有一个,也要使用大括号。
与if
对应的是else
语句。如果if
的条件值为FALSE
,则会执行else
之后的代码:
if(FALSE)
{
message("This won't execute...")
} else
{
message("but this will.")
}
## but this will.
以下规则非常重要:else
必须与if
语句的右大括号紧接在同一行。如果你把它挪到下一行,将出现错误:
if(FALSE)
{
message("This won't execute...")
}
else
{
message("and you'll get an error before you reach this.")
}
你可以反复使用if
和else
来定义多个条件。请注意,if
和else
仍然是两个独立的词——还有一个ifelse
函数,它稍有不同,我们将马上看到:
(r <- round(rnorm(2), 1))
## [1] -0.1 -0.4
(x <- r[1] / r[2])
## [1] 0.25
if(is.nan(x))
{
message("x is missing")
} else if(is.infinite(x))
{
message("x is infinite")
} else if(x > 0)
{
message("x is positive")
} else if(x < 0)
{
message("x is negative")
} else
{
message("x is zero")
}
## x is positive
与很多其他的语言不同,R有一个小技巧能对你的代码重新排序来完成条件赋值。在下例中,Re
将返回复数实部(Im
将返回虚部):
x <- sqrt(-1 + 0i)
(reality <- if(Re(x) == 0) "real" else "imaginary")
## [1] "real"
8.2.2 矢量化的if
标准的if
语句需要一个逻辑值作为参数。如果给它传递一个长度超过1的逻辑向量(请不要这样做!),R会警告你已给出多个选项,仅第一个将被使用:
if(c(TRUE, FALSE)) message("two choices")
## Warning: the condition has length > 1 and only the first element will be
## used
## two choices
由于R几乎都是矢量化的,你大概不会惊讶于矢量化的流程控制的存在,例如ifelse
函数。ifelse
有三个参数:第一个是逻辑条件向量;第二个参数值在第一个向量为TRUE
时被返回;第三个参数值在第一个向量为FALSE
时被返回。在下例中,rbinom
使用二项分布生成随机数以模拟掷硬币过程:
ifelse(rbinom(10, 1, 0.5), "Head", "Tail")
## [1] "Head" "Head" "Head" "Tail" "Tail" "Head" "Head" "Head" "Tail" "Head"
ifelse
也可在第二个和第三个参数中接受向量。它们应与第一个向量的长度相等(如果不等,那么第二个和第三个参数中的元素将被重复或忽略,以使它们与第一个参数的长度相同):
(yn <- rep.int(c(TRUE, FALSE), 6))
## [1] TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
## [12] FALSE
ifelse(yn, 1:3, -1:-12)
## [1] 1 -2 3 -4 2 -6 1 -8 3 -10 2 -12
如果条件参数中有缺失值,那么结果中的相应位置也是缺失值:
yn[c(3, 6, 9, 12)] <- NA
ifelse(yn, 1:3, -1:-12)
## [1] 1 -2 NA -4 2 NA 1 -8 NA -10 2 NA
8.2.3 多个分支
如果包含太多的else
语句就会迅速降低代码的可读性。在这种情况下,可以用switch
函数来美化它们。它的常见用法是:第一个参数为一个返回字符串的表达式,其后的参数为与第一个参数相匹配时的返回值。匹配参数必须与第一个参数完全一样(从R的2.11.0版本开始),且你可以使用大括号来执行多个表达式:
(greek <- switch(
"gamma",
alpha = 1,
beta = sqrt(4),
gamma =
{
a <- sin(pi / 3)
4 * a ^ 2
}
))
## [1] 3
如果找不到任何匹配的名字,那么switch
将(隐式地)返回NULL
:
(greek <- switch(
"delta",
alpha = 1,
beta = sqrt(4),
gamma =
{
a <- sin(pi / 3)
4 * a ^ 2
}
))
## NULL
对于这种情况,你可以在没有其他选择的情况下提供一个没有命名的参数:
(greek <- switch(
"delta",
alpha = 1,
beta = sqrt(4),
gamma =
{
a <- sin(pi / 3)
4 * a ^ 2
},
4
))
## [1] 4
switch
的第一个参数也可以是一个整数。在这种情况下,其余的参数不需要名字——如果第一个参数结果为1
,那么将返回第二个参数的结果;如果第一个参数的结果为2
,则返回第三个参数的结果,以此类推:
switch(
3,
"first",
"second",
"third",
"fourth"
)
## [1] "third"
你可能已经注意到了,在这种情况下没有默认参数。如果你要测试的整数非常大,这将相当麻烦,因为你需要提供很多参数。这时,最好是把第一个参数转换为字符串,并且使用原来的第一种语法:
switch(
as.character(2147483647),
"2147483647" = "a big number",
"another number"
)
## [1] "a big number"