R 学习笔记(6) -- R 语言编程结构


R 是一种块状结构的语言,R 语言的块(block) 由大括号划分,不过当块只包含一条语句时大括号可以省略。程序语句由换行符或者分号隔开,不过还是尽量别用分号了,每行一条语句也方便理解。


控制语句

for, while, repeat 循环, 用 break 可以跳出循环。

> x <- c(5,12,13)
> for (n in x) print(n^2)
[1] 25
[1] 144
[1] 169
> # 更正式写法,for 后面用大括号将代码块括起来
> for (n in x) {
+     print(n^2)
+ }
[1] 25
[1] 144
[1] 169
> # while 循环,break 跳出循环
> i <- 1
> while(TRUE) {
+     i <- i+4
+     if (i>10) break
+ }
> i
[1] 13
> # repeat 循环,用 break 跳出循环
> i <- 1
> repeat {
+     i <- i+4
+     if (i>10) break
+ }
> i
[1] 13

还有一个重要的语句是 next ,它会告诉解释器直接跳过本次迭代的剩余部分,直接进入循环的下一次迭代。这避免了使用复杂的 if-then-else 嵌套结构,让代码更清晰。next 有点像 python 中的 pass。

> x <- 1:10
> x
 [1]  1  2  3  4  5  6  7  8  9 10
> for (n in x) {
+     if (n%%2==1) next
+     print(n)
+ }
[1] 2
[1] 4
[1] 6
[1] 8
[1] 10

一个很有用的函数,get(),这个函数接受一个代表对象名字的字符串参数,然后返回该对象的内容。

> m <- c(1,2,3)
> u <- c(7,8,9)
> for (i in c("m","u")) {
+     z <- get(i)
+     print(z)
+ }
[1] 1 2 3
[1] 7 8 9

if-else 结构,基本结构如下:

> if (r==4) {
+    x <- 1
+ } else {
+    x <- 3
+    y <- 4
+ }

虽然 if 的执行部分只有一条语句,但大括号不可省略。if-else 结构语句与函数调用的相似之处在于,会返回表达式的值:

> x <- 2
> y <- if(x==2) x else x+1
> y
[1] 2
> y <- if(x==3) x else x+1
> y
[1] 3

R 语言的基本运算

R 语言的基本运算符:

运算符 描述
x + y 加法
x - y 减法
x * y 乘法
x / y 除法
x ^ y 乘幂
x %% y 模运算(取余)
x %/% y 整数除法
x == y 判断是否相等
x <= y 判断是否小于等于
x >= y 判断是否大于等于
x && y 标量的逻辑“与”运算
x ll y 标量的逻辑“或”运算
x & y 向量的逻辑“与”运算(x,y以及运算结果都是向量)
x l y 向量的逻辑“或”运算(x,y以及运算结果都是向量)
!x 逻辑非

参数的默认值

函数的参数可以设置一个默认值,“具名实参”如下:

> read.table
function (file, header = FALSE, sep = "", quote = "\"'", dec = ".", 
    numerals = c("allow.loss", "warn.loss", "no.loss"), row.names, 
    col.names, as.is = !stringsAsFactors, na.strings = "NA", 
    colClasses = NA, nrows = -1, skip = 0, check.names = TRUE, 
    fill = !blank.lines.skip, strip.white = FALSE, blank.lines.skip = TRUE, 
    comment.char = "#", allowEscapes = FALSE, flush = FALSE, 
    stringsAsFactors = default.stringsAsFactors(), fileEncoding = "", 
    encoding = "unknown", text, skipNul = FALSE) 
{
    if (missing(file) && !missing(text)) {
        file <- textConnection(text, encoding = "UTF-8")
        encoding <- "UTF-8"
        on.exit(close(file))
....

其中 header = FALSE意味着如果不重新指定 header 的值,则默认为 FALSE。


返回值

在函数中可以显示地调用 return 来返回值,如果不用 return 则函数默认将最后一条语句的值作为返回值。虽然调用 return 会延长执行时间,但这点时间是微不足道的,长远来看显示地调用 return 可使代码更易阅读,利大于弊。

当然也可以返回复杂对象,比如返回值是一个函数。

> x <- c(1,2,3,4,5,6,7,8,9)
> oddcount <- function(x){
+   k <- 0
+   for (n in x){
+     if (n %% 2 ==1) k <- k + 1
+   }
+   k
+ }
> oddcount(x)
[1] 5
> # 下面这种写法效果是一样的,但是显示地调用 return 更便于理解
> oddcount_new <- function(x){
+   k <- 0
+   for (n in x){
+     if (n %% 2 ==1) k <- k + 1
+   }
+   return(k)
+ }
> oddcount_new(x)
[1] 5

函数都是对象

R 中的函数是第一类对象(属于“funtction”类,函数类),函数可以作为对象来操作,创建函数:

> g <- function(x) {
+   return(x+1)
+ }
> # 查看函数 g 的内容
> g
function(x) {
  return(x+1)
}

function() 是 R 的一个内置函数,功能就是创建函数,在 function 的右边其实有两个参数,其一是创建的函数的形式参数列表,这个例子中只有一个 x,其二是函数的主体部分,函数体,本例中只有一句表达式return(x+1),第二个参数必须是表达式类。


环境和变量作用域的问题

简单来说变量被定义的环境决定了变量的作用域,顶层变量(全局变量)和局部变量,用 ls()函数可以查看当前环境下面的变量。

> w <- 12
> f <- function(y) {
+   d <- 8
+   h <- function() {
+     return(d*(w+y))
+   }
+   #查看函数 f 中的变量,局部变量
+   print(ls())
+   return(h())
+ }
> environment(f)
<environment: R_GlobalEnv>
> f(2)
[1] "d" "h" "y"
[1] 112
> # 查看全局变量
> ls()
[1] "f" "w"

在函数中对顶层变量进行更改只是临时性的,改变只发生在函数的命名空间中。

R 语言没有指针,因此有些不方便,举个例子,在Python中:

>>> x = [5,1,2,4,3]
>>> x.sort()
>>> x
[1, 2, 3, 4, 5]

Python 中对变量 x 进行排序 list.sort(x) 结果导致 x 本身发生了变化,但是在 R 中 x 本身并不会变:

> x <- c(5,1,2,4,3)
> x
[1] 5 1 2 4 3
> # 对 x 排序后 sort() 函数返回排序后的结果
> # 但 x 本身的值仍是排序之前的
> sort(x)
[1] 1 2 3 4 5
> x
[1] 5 1 2 4 3
> # 改变 x 就需要对 x 重新进行赋值
> x <- sort(x)
> x
[1] 1 2 3 4 5

超赋值运算符

R 中环境层次中某一层次的代码对它上级层次中的所有变量只有读的权限,但是利用超赋值运算符 <<- 或 assign() 函数可以对上一层级中的变量进行修改。

> f <- function(u) {
+   u <<- 2*u
+   z <- 2*z
+   print(u) # 此时输出的是作为局部变量的函数参数 u,并不是超赋值的变量 u
+   print(z)
+ }
> f(2)
Error in f(2) : 找不到对象'z'
> z <- 3
> f(2)
[1] 2
[1] 6
> # 超赋值操作在函数的上一层级创建了一个变量 u 
> u
[1] 4
> # 函数内部的 z 和此处的 z 是两个变量,前者是局部变量,后者是顶层变量
> z
[1] 3

跨级操作变量的 assign() 函数

assign() 函数也可以对上一层级的变量进行写操作,类似于超赋值运算符的作用:

> # 当前顶层变量只有 x 和 z
> ls()
[1] "x" "z"
> two <- function(u) {
+   assign("u",2*u,pos=.GlobalEnv)
+   z <- 2*z
+ }
> x
[1] 1
> z
[1] 3
> two(x)
> # 定义 two 函数,调用 assign 函数后,顶层变量多了 two 和 u
> ls()
[1] "two" "u"   "x"   "z"  
> u
[1] 2
> z
[1] 3

例子中 assign 函数中 pos=.GlobalEnv 指定了新变量 u 的命名空间是全局环境。


闭包

闭包包含一个可创建局部变量的函数,并创建另一个函数可以访问该变量。举例说明:

> counter <- function() {
+   ctr <- 0
+   f <- function() {
+     ctr <<- ctr + 1
+     cat("this count currently has value",ctr,"\n")
+     }
+   return(f)
+ }
> c1 <- counter()
> c2 <- counter()
> # c1 和 c2 是两个独立的闭包,各自创建的变量在内存中存储在不同的位置
> c1
function() {
    ctr <<- ctr + 1
    cat("this count currently has value",ctr,"\n")
    }
<environment: 0x24ce6d0>
> c2
function() {
    ctr <<- ctr + 1
    cat("this count currently has value",ctr,"\n")
    }
<environment: 0x24cf1a0>
> c1()
this count currently has value 1 
> c1()
this count currently has value 2 
> c2()
this count currently has value 1 
> c2()
this count currently has value 2 
> c2()
this count currently has value 3 
> c1()
this count currently has value 3 
> # c1 和 c2 是两个独立的计数器

每次调用 counter() 变量 ctr 都会被创建,但是每次都创建在不同的环境中,例如 c1 的 <environment: 0x24ce6d0> 和 c2 的 <environment: 0x24cf1a0>,结果使得 c1 和 c2 相互独立。


递归魔法

递归是一种解决问题的思路,递归函数会调用自己本身,大致来讲,通过写一个递归函数 f() 来解决 X 类型的问题:

  1. 将 X 类型的原始问题划分为更小的 x 类型问题。
  2. 在 f() 中对每个较小问题调用 f() 函数。
  3. 然后在 f() 中,将 2 步骤的所有结果整合起来解决 X 问题。

例如在数学上,斐波那契数列是以递归的方法来定义:

F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) (n≧2)

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233......

如果要得到第 31 个斐波那契数:

> fib <- function(n){
+   if (n==0) {
+     return(0)
+   }else if(n==1){
+       return(1)
+   }else{
+       return(fib(n-1)+fib(n-2))
+   }
+ }
> # fib(30) 返回值即第 31 个斐波那契数
> fib(30)
[1] 832040
> # 如果要得到第 100 个斐波那契数
> fib(99)
> # 机器算了几十分钟还没出结果,被我 ctrl + C 了
> # 这个算法只是演示递归思想,由于时间复杂度太高并不实用,斐波那契这类问题用动态规划算法可以快速得到结果


置换函数

R 中有一类不同寻常的置换函数,看这个例子:

> x <- c(1,2,3)
> x
[1] 1 2 3
> names(x)
NULL
> # 重点就是这个语句,把一个向量赋值给一个函数调用返回值?
> names(x) <- c('a','b','c')
> names(x)
[1] "a" "b" "c"
> x
a b c 
1 2 3 

例子中把一个向量赋值给了一个函数调用的结果,好像不合理,但实际上这可以用 R 中的置换函数的概念来解释:
names(x) <- c('a','b','c') 这句实际发生的操作是:

 > x <- "names<-"(x,value=c('a','b','c'))

这里调用了一个名为 names<-() 的函数,由于函数名中有特殊符号,用引号括起来了。

在 R 中任何左边不是标识符(即变量名)的赋值语句都可以看作是“置换函数”:

> # 例如这样一个语句
> g(u) <- v
> # R 语言会尝试用下面这种方法执行
> u <- "g<-"(u,value=v)
> # 如果事先已经定义好“g<-”函数,就可以正常执行置换函数

匿名函数

简单的说,匿名函数就是不将定义的函数赋值给一个变量,直接调用 function() 定义一个临时函数,看例子:

> z
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
> y <- apply(z,1,function(x) x/c(2,8))
> y
     [,1]  [,2] [,3]
[1,]  0.5 1.000 1.50
[2,]  0.5 0.625 0.75

匿名函数对付一些简单的问题很方便,也使得代码更容易阅读。


肚子大不可怕,可怕的是肚子里没有好东西。 --加菲猫

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • 86.复合 Cases 共享相同代码块的多个switch 分支 分支可以合并, 写在分支后用逗号分开。如果任何模式...
    无沣阅读 1,340评论 1 5
  • 原文链接:https://github.com/EasyKotlin 值就是函数,函数就是值。所有函数都消费函数,...
    JackChen1024阅读 5,941评论 1 17
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,345评论 0 17
  • 7月31号的早上我们开车去了郑州,我们开车开了5个小时才到郑州,我们在路上在一个服务区停下来了,我们在那里把各自带...
    小溪流18王家硕阅读 558评论 2 3
  • 水面如境 镜子里的世界 透明清澈如水晶 水汽氤氲 孑孓成群 劳燕分飞 枝叶弯下腰 梳洗打扮 末了 对着镜子中的自己...
    EchoWang_1ee8阅读 158评论 0 0