8.2 流程控制

我们一般不满足于仅仅逐行地执行代码,而是希望更好地控制它们的执行流程。这就意味着只有在某些条件满足后你会才执行一些代码。

8.2.1 ifelse

最简单的流程控制逻辑是使用ifif接受一个逻辑值(更准确地说是一个长度为1的逻辑向量)作为参数,且当该值为TRUE时才会执行下一条语句:

  1. if(TRUE) message("It was true!")
  2. ## It was true!
  3. if(FALSE) message("It wasn't true!")

if的条件中不允许缺失值,这样做会抛出一个错误:

  1. if(NA) message("Who knows if it was true?")
  2. ## Error: missing value where TRUE/FALSE needed

如果你的条件中可能会出现缺失值,先用is.na来测试它:

  1. if(is.na(NA)) message("The value is missing!")
  2. ## The value is missing!

当然,大部分时候你都不会直接传入TRUEFALSE值,而是传递一个变量或表达式——因为如果知道该语句将被提前执行,就不需要if语句了。在下例中,runif(1)将在0和1之间生成一个均匀分布的随机数。如果该值超过0.5,则显示以下消息:

  1. if(runif(1) > 0.5) message("This message appears with a 50% chance.")

如果你想有条件地执行多个语句,就把它们括在大括号中:

  1. x <- 3
  2. if(x > 2)
  3. {
  4. y <- 2 * x
  5. z <- 3 * y
  6. }

为了使代码更清晰,一些编程风格指南建议:即使条件执行语句只有一个,也要使用大括号。

if对应的是else语句。如果if的条件值为FALSE,则会执行else之后的代码:

  1. if(FALSE)
  2. {
  3. message("This won't execute...")
  4. } else
  5. {
  6. message("but this will.")
  7. }
  8. ## but this will.

以下规则非常重要:else必须与if语句的右大括号紧接在同一行。如果你把它挪到下一行,将出现错误:

  1. if(FALSE)
  2. {
  3. message("This won't execute...")
  4. }
  5. else
  6. {
  7. message("and you'll get an error before you reach this.")
  8. }

你可以反复使用ifelse来定义多个条件。请注意,ifelse仍然是两个独立的词——还有一个ifelse函数,它稍有不同,我们将马上看到:

  1. (r <- round(rnorm(2), 1))
  2. ## [1] -0.1 -0.4
  3. (x <- r[1] / r[2])
  4. ## [1] 0.25
  5. if(is.nan(x))
  6. {
  7. message("x is missing")
  8. } else if(is.infinite(x))
  9. {
  10. message("x is infinite")
  11. } else if(x > 0)
  12. {
  13. message("x is positive")
  14. } else if(x < 0)
  15. {
  16. message("x is negative")
  17. } else
  18. {
  19. message("x is zero")
  20. }
  21. ## x is positive

与很多其他的语言不同,R有一个小技巧能对你的代码重新排序来完成条件赋值。在下例中,Re将返回复数实部(Im将返回虚部):

  1. x <- sqrt(-1 + 0i)
  2. (reality <- if(Re(x) == 0) "real" else "imaginary")
  3. ## [1] "real"

8.2.2 矢量化的if

标准的if语句需要一个逻辑值作为参数。如果给它传递一个长度超过1的逻辑向量(请不要这样做!),R会警告你已给出多个选项,仅第一个将被使用:

  1. if(c(TRUE, FALSE)) message("two choices")
  2. ## Warning: the condition has length > 1 and only the first element will be
  3. ## used
  4. ## two choices

由于R几乎都是矢量化的,你大概不会惊讶于矢量化的流程控制的存在,例如ifelse函数。ifelse有三个参数:第一个是逻辑条件向量;第二个参数值在第一个向量为TRUE时被返回;第三个参数值在第一个向量为FALSE时被返回。在下例中,rbinom使用二项分布生成随机数以模拟掷硬币过程:

  1. ifelse(rbinom(10, 1, 0.5), "Head", "Tail")
  2. ## [1] "Head" "Head" "Head" "Tail" "Tail" "Head" "Head" "Head" "Tail" "Head"

ifelse也可在第二个和第三个参数中接受向量。它们应与第一个向量的长度相等(如果不等,那么第二个和第三个参数中的元素将被重复或忽略,以使它们与第一个参数的长度相同):

  1. (yn <- rep.int(c(TRUE, FALSE), 6))
  2. ## [1] TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
  3. ## [12] FALSE
  4. ifelse(yn, 1:3, -1:-12)
  5. ## [1] 1 -2 3 -4 2 -6 1 -8 3 -10 2 -12

如果条件参数中有缺失值,那么结果中的相应位置也是缺失值:

  1. yn[c(3, 6, 9, 12)] <- NA
  2. ifelse(yn, 1:3, -1:-12)
  3. ## [1] 1 -2 NA -4 2 NA 1 -8 NA -10 2 NA

8.2.3 多个分支

如果包含太多的else语句就会迅速降低代码的可读性。在这种情况下,可以用switch函数来美化它们。它的常见用法是:第一个参数为一个返回字符串的表达式,其后的参数为与第一个参数相匹配时的返回值。匹配参数必须与第一个参数完全一样(从R的2.11.0版本开始),且你可以使用大括号来执行多个表达式:

  1. (greek <- switch(
  2. "gamma",
  3. alpha = 1,
  4. beta = sqrt(4),
  5. gamma =
  6. {
  7. a <- sin(pi / 3)
  8. 4 * a ^ 2
  9. }
  10. ))
  11. ## [1] 3

如果找不到任何匹配的名字,那么switch将(隐式地)返回NULL

  1. (greek <- switch(
  2. "delta",
  3. alpha = 1,
  4. beta = sqrt(4),
  5. gamma =
  6. {
  7. a <- sin(pi / 3)
  8. 4 * a ^ 2
  9. }
  10. ))
  11. ## NULL

对于这种情况,你可以在没有其他选择的情况下提供一个没有命名的参数:

  1. (greek <- switch(
  2. "delta",
  3. alpha = 1,
  4. beta = sqrt(4),
  5. gamma =
  6. {
  7. a <- sin(pi / 3)
  8. 4 * a ^ 2
  9. },
  10. 4
  11. ))
  12. ## [1] 4

switch的第一个参数也可以是一个整数。在这种情况下,其余的参数不需要名字——如果第一个参数结果为1,那么将返回第二个参数的结果;如果第一个参数的结果为2,则返回第三个参数的结果,以此类推:

  1. switch(
  2. 3,
  3. "first",
  4. "second",
  5. "third",
  6. "fourth"
  7. )
  8. ## [1] "third"

你可能已经注意到了,在这种情况下没有默认参数。如果你要测试的整数非常大,这将相当麻烦,因为你需要提供很多参数。这时,最好是把第一个参数转换为字符串,并且使用原来的第一种语法:

  1. switch(
  2. as.character(2147483647),
  3. "2147483647" = "a big number",
  4. "another number"
  5. )
  6. ## [1] "a big number"