R语言的控制结构可以对R程序进行流程控制,这些控制结构与你在其它编程语言中看到的非常相似。基本的结构如下所示。
因此,大多数控制结构不是那些你在交互式会话中使用的,也不像在R语言的命令行里执行命令,这些控制结构通常是你写R程序或R函数时会用到的。
条件控制 if
第一个结构是 if 结构。这是一个带有 else 的 if 结构,它可以用来测试逻辑条件,并基于条件的真伪来执行命令。如果条件成立,程序会做某件事,否则就做另一件。这是一种典型的结构,而 else 部分不是必须的。因此当你需要条件成立时执行某命令,只需要 if 语句即可。但如果你需要执行某个备选命令,那么就需要 else 部分。如果你需要检查多个条件,那你可以在 if 后面增加 else if 和 else 的结构。像这样的结构里,可以有任意数量的 else if,但必须以的 else 语句结尾。
if(<condition>) { ## do something} else { ## do something else}if(<condition1>) { ## do something} else if(<condition2>) { ## do something different} else { ## do something different}
在R语言里有多种方式可以构建 if/else 的结构,如果你没见过类似这样的结构的话,那它确实与其它语言略有不同。下面第一个是比较标准的结构。
if(x > 3) { y <- 10 ##如果 x 大于3,y 就等于10} else { y <- 0 ##否则如果 x 不大于3,y就等于0 } ##根据 x 与 3 比较的不同结果,赋给 y 不同的值
但在R语言中,你可以使用另外一种方法,可以让 y 等于整个 if/else 结构。这也是个有效的表述。有时候你在读代码时,因为它会让你明白整个 if/else 结构就为了对 y 进行赋值,所以这种类型的写法是很有用的。
if(x > 3) { 10} else { 0}
当然,else 部分也不是必须的。可以只是测试条件成立,然后执行紧接着的大括号里面的命令,或者刚巧条件不成立,那就什么也不做。如果有需要,你可以在一行里测试多个条件。
if(<condition1>) {}if(<condition2>) {}循环结构一 for
下面这个结构是 for 结构,这可能会是你在R语言中用到的最常见的循环运算符。基本思路是:设一个循环下标,通常命名为 i, 当然如果有多个循环也可以用 j、 k、 l 等等,循环下标通常会遍历一个整数的数列。当循环结束时,它就继续执行下一段代码主体。
举例来说,这里有一个 for 循环,它从数字1遍历到10(中间用冒号隔开),然后创建了这个1到10的数列。
for(i in 1:10) { print(i) ##在每次迭代时输出 i}
在R语言里可以用不同方式来使用 for 结构,而且R语言在索引R对象时很灵活。
##用字母a、b、c、d构建一个字符向量,下面这几个 for 循环例子是等价的x <- c("a", "b", "c", "d") ##第一个例子:用从1到4的整数数列作为下标 i 的取值,每次打印出 x 的第 i 个元素,因此这个循环就打印出"a", "b", "c", "d"for(i in 1:4) { print(x)} ##第二个例子:使用 seq_along 函数,它的输入是一个向量,之后它会创建一个与该向量等长的整数数列。 x 向量,长度为4,所以它会创建一个从1到4的整数数列,这和用1:4的语句获取数列的结果是完全一样的。但这是基于变量 x 的长度生成生成的数列,然后下标变量 i 遍历这个数组,打印出"a", "b", "c", "d"for(i in seq_along(x)) { print(x)} ##第三个例子:取下标变量 letter,它会从向量本身取值(没有理由说下标变量一定要是整数),可以从任意向量中提取元素,所以这个 for 循环遍历 x 向量中的字母a, b, c, d,然后就打印出字母本身for(letter in x) { print(letter)} ##最后这个例子除了没带{}跟第一个一样,如果一个 for 循环里只包含单个表达式,那你将之放一行,并省略{},这样做有时候更好用,因为更紧凑一些for(i in 1:4) print(x)
for 循环可以嵌套,所以可以把一个 for 循环放到另一个 for 循环里面。
通常的例子是,有一个二维矩阵,需要对它做一个行循环再做一个列循环。这里外层循环的下标为 i 作为行循环,再用 seq_len()函数(这个函数的输入是一个整数,这个例子里刚好是 x 的行号)创建一个整数数列。
这个特定的矩阵有两行,因此会产生一个1和2组成的数列。类似地,嵌套循环里的 j 下标用列号产生数列,这个矩阵有三列,因此这个seq_len()会产生1到3的整数数列。然后这个双层嵌套的循环就打印出这个矩阵里所有的元素。但一点需要注意:虽然理论上没问题但循环嵌套较好不要超过两到三层,因为超过的话代码读起来会有些困难。如果代码有两到三层的嵌套,会很难懂。虽然有时候这么做很合逻辑,但很多时候总有替代的办法,比如说用函数,等。
x <- matrix(1:6, 2, 3)for(i in seq_len(nrow(x))) { for(j in seq_len(ncol(x))) { print(x[i, j]) }}while
while循环是R中另一种主要的循环结构,其基本原理是,它有一个逻辑表达式,循环按照此逻辑表达式的值来运行。
这里有一个很简单的循环:
count <- 0 ##将计量变量初始值设定为0while(count < 10) { print(count) count <- count + 1} ##当计量变量小于10时,该循环输出该计数变量,然后将这个计数变量的值加1,一旦计数变量的值达到10,循环就会停止
从该循环可以看出, while 循环很有用,因为它通常比较方便阅读。可以清楚地看到,当计数变量的值大于或等于10时,这一循环就会停止运行,接着会运行下一段代码主体。这些都表明 while 循环是比较易读的。当然,使用 while 循环时必须小心一点,因为从技术层面来说 while 循环是无限循环,所以要确保终止的条件一定会达成,否则程序永远不会结束。在上面这个例子中,很显然循环最后会终止。但是如果代码再复杂一点就不容易确认 while 循环是否会停止了。如果想要保险一点,就使用 for 循环,它对循环执行次数具有硬性限制的循环。但并不是说你不要使用 while 循环,只不过在你使用它们时务必要谨慎。
在 while 循环中我们可以使用逻辑运算符来验证多重条件或者各种类型的结构,例如 if 语句。
z <- 5 ##把变量 z 的初始值设为5while(z >= 3 && z <= 10) { print(z) ##当 z 的取值介于3和10之间时,程序会输出 z 的值 coin <- rbinom(1, 1, 0.5) #rbinom()模拟抛掷均匀硬币 if(coin == 1) { ## random walk z <- z + 1 ##如果硬币抛出一个1就给 z 加1 } else { z <- z - 1 ##如果抛出0或其它值,将把 z 减1 }}
这是一个小型的随机游戏,z 的值会根据抛硬币的结果而变大或变小。这里可以看到,这段 while 循环包含了随机数的生成,所以我们很难判断它何时会结束,z 值会上下波动直至它最终达到10或者小于等于3。这段代码看起来挺好的,但要小心,它的运行时间可能会很长。还有一个技术层面的事情需要注意,当R检验具有多个表达式的条件时,条件总是自左向右被检验,所以程序会检查最左边的条件是否为真,然后在检查下一个条件,都为真时程序会继续进行,进入循环的主体。
循环结构 repeat
repeat 结构发起的基本都是无限循环,我们一般不会把这个结构归为R中常用的控制结构,但我们仍然会在某些场合用到它。退出 repeat 循环的方法是调用 break 函数。显而易见,除非你想永远运行程序,否则你将不得不在某个节点调用 break 函数。这里有一个简单的例子:
x0 <- 1 ##把 x0 的初始值定为1tol <- 1e-8 ##设定一个容差,其值为10的负8次方repeat { x1 <- computeEstimate() ##估算 x 的值 if(abs(x1 - x0) < tol) { break ##如果 x1 与 x0 的差的值小于容差,循环停止 }else { x0 <- x1 }}
在许多类型的优化算法中,这是一种常见的公式化做法。举例来说,如果你想解一组方程或者求函数的较大值,通常会一次又一次的进行迭代,当你的估计值彼此越来越接近,循环就会终止,这表明你已经接近目标函数的较大值或者最小值。理论上来说,这是一个完美而合理的构造,但如果想要持续地循环利用这一算法知道两个值足够接近。
问题就来了,首先,我们需要保证该算法能够收敛,但并非所有的算法都具有这种收敛的特性。其次,算法的效果还部分取决于容差,一般来说,容差越小,循环运行的时间越长。通常来说,难以预测循环的时间,这种算法可能会有点危险,理论上来说它甚至可以永远运行,而你没有办法保证程序会在某个节点终止。尽管从理论上来说这一结构是正确的,通常我们不认为它是一个好方法,所以较好是使用 for 循环。
next,return
最后2个控制结构要素是 next 函数以及 return 函数。 next 函数可以在任何类型的循环结构里使用,主要用来跳过某次迭代。这里有一个基本的 for 循环,该循环会运行100次迭代,但想要跳过开始的20次迭代只执行第21次到第100次迭代,所以写了一个简单的 if 条件(如果 i 小于等于20就跳过迭代)调用了 next 函数,然后进入下一次迭代。故 next 函数是一种跳过迭代的方法,而 break 函数是一种完全跳出循环的方法。
for(i in 1:100) {if(i <= 20) { ## 如果 i 小于等于20就调用 next 函数跳过迭代 next} ## 如果 i 大于20就执行 for 循环中 next 语句后面的代码}
return 函数是另一种用来跳出循环的函数,主要用来跳出一个函数(会使之跳出整个函数),然后返回你传递给它的值。但要注意 return 函数也可以终端程序的运行。
|
请发表评论