Code
x <- 85
if (x > 90) {
"A"
} else if (x > 80) {
"B"
} else if (x > 50) {
"C"
} else {
"F"
}[1] "B"
通过本章学习,你将能够:
在默认情况下,R 代码是按顺序执行的:
1 + 1
2 + 2
3 + 3
👉 从上到下依次执行
但在实际问题中,我们往往需要:
👉 这就需要 控制流(Control Flow)
控制流是指程序在运行过程中,根据条件或规则,决定“哪些代码执行、哪些跳过、执行多少次”的机制。
if (condition) {
执行内容
}或:
if (condition) {
条件为真执行
} else {
条件为假执行
}示例:成绩等级判断
x <- 85
if (x > 90) {
"A"
} else if (x > 80) {
"B"
} else if (x > 50) {
"C"
} else {
"F"
}[1] "B"
👉 if 只能处理 单个值(标量)
x <- c(80, 90, 70)
# if (x > 80) ❌ 会报错或只取第一个值当数据是向量(多个值)时,应使用:
ifelse(condition, yes, no)示例:奇偶判断
x <- 1:10
ifelse(x %% 2 == 0, "even", "odd") [1] "odd" "even" "odd" "even" "odd" "even" "odd" "even" "odd" "even"
核心理解
ifelse = 对每个元素逐个判断
👉 和 if 的区别:
| 结构 | 适用对象 |
|---|---|
| if | 单个值 |
| ifelse | 向量 |
基本语法: switch 结构:多选一的“菜单”
当你有几个明确的、具体的选项时,用 switch 最方便。它就像自动售货机:按 A1 出可乐,按 A2 出雪碧,按 B1 出薯片。
switch(选择的按钮,
A1 = 可乐,
A2 = 雪碧,
B1 = 薯片,
默认结果 # 如果都不匹配,执行这个
)👉 根据“选项”返回不同结果(类似菜单)
x <- "a"
switch(x,
a = "option 1",
b = "option 2",
c = "option 3",
stop("Invalid value")
)[1] "option 1"
👉 stop() 就像是在代码里拉下紧急刹车。
当 R 运行到 stop() 这行代码时,它会立刻停止运行,并且把括号里的文字作为红色报错信息打印在控制台上,提醒你出了什么问题。 如果不加 stop(),当用户输入了错误的选项时,switch 会悄悄返回一个 NULL(空值),代码不会报错,会继续往下跑。但这可能是一个隐患,因为后面的代码如果用到这个结果,遇到 NULL 可能会算出莫名其妙的数据,而且很难排查原因。
用了 stop(),就是“早发现,早解决”。遇到不对劲的情况,立刻大声报错停下来,防止错误的数据悄悄流传下去。
x <- "b"
switch(x,
a = "苹果",
b = "香蕉",
c = "橘子",
stop("没有这个选项哦!") # 如果输入的不是a/b/c,就会报错提示
)
# 结果会返回 "香蕉"
switch 主要用于“字符精确匹配”,不适合数值大小比较!比如:判断 score >= 90 是A等,这种范围比较不能用switch,只能用 if / else if。 switch 只能做:x 是 “a” 就给A,是 “b” 就给B 这种一对一的死匹配。
👉 当你需要明确知道要重复几次,或者要挨个处理一组数据时,就用 for 循环。就像老师点名:从1号同学开始,挨个念名字,直到念完最后一个同学。
for (变量 in 序列) {
# 重复执行的代码
}例如:
在 R 语言的 for 循环语法中,in 是一个关键字(保留字),用于定义循环变量的取值范围或来源。
它的具体含义和作用如下: in 的字面意思是 “在…之中” 或 “取自…”。
整行代码 for (变量 in 序列) 读起来就是:
“对于 序列 之中的 每一个 变量,执行以下操作…”
它在循环结构中起到了连接和指定来源的作用:
in:告诉 R 解释器,左边的变量是从右边的序列里逐个取值的。当 R 看到这个结构时,它的执行过程是:
in 右边的序列。in 左边的变量。for (i in 1:3) {
print(i)
}[1] 1
[1] 2
[1] 3
names <- c("小明", "小红", "小华")
for (name in names) {
print(paste("你好,", name))
}[1] "你好, 小明"
[1] "你好, 小红"
[1] "你好, 小华"
# 结果:
# [1] "你好, 小明"
# [1] "你好, 小红"
# [1] "你好, 小华"for (i in 1:10) {
if (i < 3) next # 跳过本次
print(i)
if (i >= 5) break # 终止循环
}[1] 3
[1] 4
[1] 5
| 语句 | 作用 |
|---|---|
| next | 跳过本次循环 |
| break | 结束整个循环 |
for 循环取值的重要原则
b <- Sys.time()
results <- c()
for (i in 1:100000) {
results <- c(results, i * 2)
}
e <- Sys.time()
e-b
# 先准备好一个长度为10000的空盒子(数值型)
b <- Sys.time()
results <- vector("numeric", length = 10000)
for (i in 1:100000) {
# 每次只要把结果放进对应的位置即可,非常快
results[i] <- i * 2
}
e <- Sys.time()
e-b
为什么?
避免在循环中不断扩展对象(效率很低)
特殊情况处理
seq_along(x)seq_along(x) 主要用于 编写安全的 for 循环,特别是当你不确定被循环的对象 x 是否为空(长度为 0)时。
它的核心作用是:生成一个与对象长度相等的整数序列(1, 2, 3, …),且当对象为空时,返回空序列(而不是 c(1, 0))
x <- c() # 即使向量是空的
for (i in seq_along(x)) { # seq_along(c()) 是空的,循环直接不执行,安全!
print(x[i])
}
比:
1:length(x)更安全(避免长度为0时报错)
x <- c() # 假设向量是空的,长度为0
for (i in 1:length(x)) { # 1:0 会变成 c(1, 0),循环会倒着跑两次,大错特错!
print(x[i])
}
主要用途:用于不确定循环次数,但需要在每次循环开始前检查某个条件是否成立的场景。只要条件为 TRUE,循环就会继续。
特点:
入口控制:在执行循环体之前先测试条件。
可能一次都不执行:如果初始条件就是 FALSE,循环体内的代码直接跳过。 典型场景:
迭代算法(如牛顿法求根),直到误差足够小为止。
寻找满足特定条件的第一个元素。
模拟过程,直到达到特定状态。
while (condition) {
执行内容
}示例:
i <- 0
while (i < 5) {
print("I'm happy")
i <- i + 1
}[1] "I'm happy"
[1] "I'm happy"
[1] "I'm happy"
[1] "I'm happy"
[1] "I'm happy"
主要用途: 用于需要无限循环直到内部明确触发退出指令的场景。它本质上是一个死循环,必须在循环体内部使用 break 语句来退出,否则会一直运行下去。
特点:
典型场景:
repeat {
执行内容
if (条件) break
}示例:
i <- 0
repeat {
print("Hi")
i <- i + 1
if (i > 4) break
}[1] "Hi"
[1] "Hi"
[1] "Hi"
[1] "Hi"
[1] "Hi"
| 类型 | 特点 |
|---|---|
| while | 条件控制 |
| repeat | 无限循环 + break 退出 |
👉 很重要的一点:
R 更推荐“向量化”,而不是循环
例如:
# 不推荐
out <- c()
for (i in 1:10) {
out[i] <- i^2
}
# 推荐
(1:10)^2 [1] 1 4 9 16 25 36 49 64 81 100
给定一个变量 score,请编写 R 代码实现成绩等级划分:
要求
if / else if / elsescore 值并输出对应等级示例数据
score <- 85给定一个操作指令 action 和两个数字 a、b,请使用 switch 实现一个简单的计算器:
如果指令是 "add",就执行加法 a + b
如果指令是 "subtract",就执行减法 a - b
如果指令是 "multiply",就执行乘法 a * b
如果指令是 "divide",就执行除法 a / b
如果输入了其他乱七八糟的指令,使用 stop("不支持该操作!") 报错停止
请使用 for 循环,计算从 1 加到 100 的总和。
library(shiny)
library(shinythemes)
library(shinyjs)
library(DT)
library(dplyr)
# ==================== 问题数据 ====================
questions <- list(
# 1-10 if / ifelse / case_when
list(id=1, question="在R中,哪一种语句最适合进行单个条件判断?",
options=c("for", "if", "while", "repeat"), correct=2,
explanation="if 语句用于单个条件判断。"),
list(id=2, question="下面哪一项是 if 语句的正确基本结构?",
options=c("if 条件 {代码}", "if (条件) {代码}", "if [条件] {代码}", "if <条件> {代码}"), correct=2,
explanation="R 中 if 的标准写法是 if (条件) {代码}。"),
list(id=3, question="x <- 5;执行 if (x > 3) {'yes'} 会返回什么?",
options=c("\"no\"", "\"yes\"", "TRUE", "FALSE"), correct=2,
explanation="因为 5 > 3 条件成立,所以返回 'yes'。"),
list(id=4, question="ifelse() 最适合处理哪类问题?",
options=c("多个语句循环执行", "函数定义", "向量化条件判断", "文件读取"), correct=3,
explanation="ifelse() 常用于向量化条件判断。"),
list(id=5, question="x <- c(2, 5, 8);ifelse(x > 4, 'A', 'B') 的结果是什么?",
options=c("c('A','A','A')", "c('B','A','A')", "c('B','B','A')", "c('A','B','A')"), correct=2,
explanation="2 不大于 4,所以是 B;5 和 8 大于 4,所以是 A。"),
list(id=6, question="在 dplyr 中,哪个函数常用于多条件分类?",
options=c("ifelse()", "switch()", "case_when()", "repeat()"), correct=3,
explanation="case_when() 适合多条件分类判断。"),
list(id=7, question="下面关于 case_when() 的说法正确的是?",
options=c("只能处理一个条件", "常用于向量化多条件判断", "只能用于循环", "不能返回字符"), correct=2,
explanation="case_when() 是 dplyr 中常见的多条件向量化判断函数。"),
list(id=8, question="x <- 80,若将成绩分为:>=90优秀,>=60及格,否则不及格,最适合用什么?",
options=c("repeat", "switch", "case_when 或 if...else if...else", "for"), correct=3,
explanation="这种多分支判断适合 case_when() 或 if...else if...else。"),
list(id=9, question="if 后面的条件结果通常应是什么类型?",
options=c("numeric", "character", "logical", "list"), correct=3,
explanation="if 的条件一般应为逻辑值 TRUE/FALSE。"),
list(id=10, question="下面哪句代码表示“如果 x 大于 0,则输出 positive,否则输出 non-positive”?",
options=c("if (x > 0) 'positive' else 'non-positive'",
"if x > 0 then 'positive' else 'non-positive'",
"if [x > 0] 'positive' else 'non-positive'",
"ifelse x > 0 'positive' 'non-positive'"), correct=1,
explanation="R 中 if...else 的标准写法为 if (条件) 表达式 else 表达式。"),
# 11-16 switch
list(id=11, question="switch() 语句通常适合哪种场景?",
options=c("重复执行代码", "根据一个值匹配多个分支", "读取数据框", "绘图"), correct=2,
explanation="switch() 适用于根据一个值选择不同分支。"),
list(id=12, question="在R中,switch('a', a='苹果', b='香蕉') 的结果是什么?",
options=c("'苹果'", "'香蕉'", "NULL", "报错"), correct=1,
explanation="因为匹配到了 a,所以返回 '苹果'。"),
list(id=13, question="下面关于 switch() 的说法,哪项正确?",
options=c("特别适合连续数值比较", "适合有限个固定选项匹配", "只能处理逻辑值", "只能在循环中使用"), correct=2,
explanation="switch() 更适合固定值匹配,而不是复杂区间判断。"),
list(id=14, question="若 choice <- 'B',switch(choice, A=1, B=2, C=3) 的结果是?",
options=c("1", "2", "3", "NULL"), correct=2,
explanation="choice 为 B,因此返回 2。"),
list(id=15, question="下列哪种任务最适合 switch()?",
options=c("判断年龄是否大于18", "根据月份编号返回月份名称", "计算1到100的和", "重复直到条件满足"), correct=2,
explanation="switch() 很适合做固定选项映射,如编号到名称。"),
list(id=16, question="如果 switch() 没有匹配到选项,通常返回什么?",
options=c("FALSE", "0", "NULL", "NA"), correct=3,
explanation="switch() 在无匹配时通常返回 NULL。"),
# 17-25 for 循环
list(id=17, question="for 循环最适合做什么?",
options=c("单次条件判断", "重复执行一组代码", "定义数据框", "输出图形"), correct=2,
explanation="for 循环用于按顺序重复执行代码。"),
list(id=18, question="下面哪一个是正确的 for 循环结构?",
options=c("for i in 1:5 { }", "for (i in 1:5) { }", "for [i in 1:5] { }", "for (1:5 in i) { }"), correct=2,
explanation="R 中 for 循环标准格式是 for (变量 in 序列) {代码}。"),
list(id=19, question="for (i in 1:3) print(i) 会依次输出什么?",
options=c("0 1 2", "1 2 3", "1 2", "3 2 1"), correct=2,
explanation="for 会按顺序遍历 1, 2, 3。"),
list(id=20, question="下面代码的作用是什么? sum <- 0; for (i in 1:4) sum <- sum + i",
options=c("计算1到4的乘积", "计算1到4的和", "计算4次随机数", "创建长度为4的向量"), correct=2,
explanation="该循环逐步累加 i,结果是 1+2+3+4。"),
list(id=21, question="若 x <- c(10, 20, 30),for (i in x) print(i) 会遍历什么?",
options=c("1 2 3", "10 20 30", "x 的列名", "报错"), correct=2,
explanation="for 会依次遍历向量 x 中的元素。"),
list(id=22, question="在 for 循环中,常用于存储每次结果的做法是?",
options=c("提前创建向量或列表", "必须使用 data.frame", "只能 print()", "不需要对象"), correct=1,
explanation="通常会预先创建向量/列表来保存循环结果。"),
list(id=23, question="以下哪段代码可以创建平方数向量 1,4,9,16,25?",
options=c(
"for (i in 1:5) x <- i^2",
"x <- c(); for (i in 1:5) x[i] <- i^2",
"x <- 1:5^2",
"square(1:5)"
), correct=2,
explanation="需要逐个把 i^2 存入向量 x。"),
list(id=24, question="for 循环中的 i 通常表示什么?",
options=c("固定常数", "循环变量", "函数名", "数据框"), correct=2,
explanation="i 是循环变量,用于依次取序列中的值。"),
list(id=25, question="for (i in seq_along(x)) 的优点是什么?",
options=c("比 1:length(x) 更稳妥", "一定更快", "只能用于列表", "可以替代 if"), correct=1,
explanation="seq_along(x) 在 x 为空时更安全。"),
# 26-31 while / repeat
list(id=26, question="while 循环的特点是什么?",
options=c("固定次数执行", "条件满足时持续执行", "只能执行一次", "必须与 switch 配合"), correct=2,
explanation="while 会在条件为 TRUE 时持续执行。"),
list(id=27, question="下面哪一个是正确的 while 结构?",
options=c("while 条件 {代码}", "while (条件) {代码}", "while [条件] {代码}", "while <条件> {代码}"), correct=2,
explanation="R 中 while 的标准格式是 while (条件) {代码}。"),
list(id=28, question="x <- 1;while (x < 4) {x <- x + 1} 结束后 x 等于多少?",
options=c("3", "4", "5", "1"), correct=2,
explanation="x 依次变为 2、3、4,当 x<4 不成立时停止。"),
list(id=29, question="repeat 循环通常需要搭配什么来终止?",
options=c("next", "break", "return", "stopifnot"), correct=2,
explanation="repeat 是无限循环结构,通常要用 break 跳出。"),
list(id=30, question="下列关于 while 和 repeat 的说法,正确的是?",
options=c("while 不需要条件", "repeat 自带终止条件", "repeat 通常需手动 break", "两者完全一样"), correct=3,
explanation="repeat 本身没有终止条件,一般靠 break 退出。"),
list(id=31, question="哪种情形更适合用 while?",
options=c("已经明确循环次数", "不知道次数,直到条件满足为止", "只做一次判断", "做固定映射"), correct=2,
explanation="while 适合未知循环次数、依条件结束的任务。"),
# 32-36 break / next
list(id=32, question="break 在循环中的作用是什么?",
options=c("跳过本次,进入下一次", "立即终止整个循环", "重新开始循环", "暂停程序"), correct=2,
explanation="break 用于直接退出整个循环。"),
list(id=33, question="next 在循环中的作用是什么?",
options=c("终止整个循环", "跳过当前这一次,进入下一次循环", "返回函数值", "删除对象"), correct=2,
explanation="next 会跳过本轮后续代码,直接进入下一轮。"),
list(id=34, question="for (i in 1:5) { if (i == 3) next; print(i) } 会输出什么?",
options=c("1 2 3 4 5", "1 2 4 5", "3", "1 2"), correct=2,
explanation="当 i=3 时执行 next,跳过 print(3)。"),
list(id=35, question="for (i in 1:5) { if (i == 3) break; print(i) } 会输出什么?",
options=c("1 2", "1 2 3", "1 2 4 5", "3 4 5"), correct=1,
explanation="当 i=3 时 break,循环立即终止,所以只输出 1 和 2。"),
list(id=36, question="break 和 next 的主要区别是?",
options=c("break 跳过一次,next 终止循环",
"break 终止循环,next 跳过当前一次",
"两者完全一样",
"break 用于if,next用于for"), correct=2,
explanation="break 直接结束循环,next 只跳过当前这轮。"),
# 37-40 综合
list(id=37, question="下面哪种任务最适合 ifelse() 而不是 for?",
options=c("逐个打印1到100", "对一个向量中的元素进行奇偶分类", "重复读取文件", "无限循环直到输入正确"), correct=2,
explanation="向量化分类任务更适合 ifelse()。"),
list(id=38, question="下面哪种任务最适合 for 循环?",
options=c("判断单个成绩是否及格", "把一个向量所有元素逐个平方后保存", "对固定选项做菜单映射", "多条件分组"), correct=2,
explanation="逐个处理元素并保存结果,是 for 的典型场景。"),
list(id=39, question="若要根据数字1、2、3分别返回“春”“夏”“秋”,更适合用?",
options=c("switch()", "while()", "repeat()", "ifelse()"), correct=1,
explanation="固定编号映射最适合 switch()。"),
list(id=40, question="控制流(control flow)的核心作用是什么?",
options=c("定义数据类型", "决定代码按什么条件和顺序执行", "创建图形", "读取CSV文件"), correct=2,
explanation="控制流决定程序执行路径,包括判断、分支和循环。")
)
# ==================== UI ====================
ui <- fluidPage(
theme = shinytheme("cerulean"),
useShinyjs(),
titlePanel("R控制流测验 (黄利东设计)"),
sidebarLayout(
sidebarPanel(
wellPanel(
h4("学生身份认证"),
textInput("student_name", "姓名:", ""),
textInput("student_id", "学号:", ""),
helpText("提示:提交后成绩将自动记录。")
),
hr(),
actionButton("submit", "提交答案", class = "btn-primary", style="width:100%"),
br(), br(),
actionButton("reset", "重新开始", style="width:100%"),
br(), br(),
h4("说明"),
p("1. 必须填写姓名和学号。"),
p("2. 学号需为13位。"),
p("3. 提交后下方会显示得分和错题解析。")
),
mainPanel(
uiOutput("auth_check_ui"),
uiOutput("question_ui"),
hr(),
uiOutput("result_ui"),
DTOutput("explanation_table")
)
)
)
# ==================== SERVER ====================
server <- function(input, output, session) {
submitted <- reactiveVal(FALSE)
is_identified <- reactive({
name_ok <- nzchar(trimws(input$student_name))
id_ok <- nchar(trimws(input$student_id)) == 13
name_ok && id_ok
})
output$auth_check_ui <- renderUI({
sid <- trimws(input$student_id)
sname <- trimws(input$student_name)
if (!nzchar(sname)) {
h4("请输入姓名", style="color:#f0ad4e; text-align:center;")
} else if (nchar(sid) < 13) {
h4(paste0("学号位数不足(当前 ", nchar(sid), "/13 位)"),
style="color:#f0ad4e; text-align:center;")
} else if (nchar(sid) > 13) {
h4("警告:学号超过13位,请检查是否输入错误",
style="color:#d9534f; text-align:center;")
} else {
h4("✅ 身份验证成功,请开始答题", style="color:#5cb85c; text-align:center;")
}
})
output$question_ui <- renderUI({
if (!is_identified()) return(NULL)
tagList(
h4("--- 答题区 ---", style="text-align:center; color:#999;"),
lapply(questions, function(q) {
radioButtons(
inputId = paste0("q", q$id),
label = paste0(q$id, ". ", q$question),
choices = setNames(seq_along(q$options), q$options),
selected = character(0)
)
})
)
})
observeEvent(input$submit, {
if (!is_identified()) {
showModal(modalDialog("请先填写完整信息!", title = "提醒", easyClose = TRUE))
return()
}
if (submitted()) {
showModal(modalDialog("您已经提交过了,请刷新页面或点击重新开始。", title = "提醒", easyClose = TRUE))
return()
}
score <- 0
wrong_ids <- c()
for (q in questions) {
ans <- input[[paste0("q", q$id)]]
user_ans <- if (!is.null(ans)) as.numeric(ans) else NA
if (!is.na(user_ans) && user_ans == q$correct) {
score <- score + 1
} else {
wrong_ids <- c(wrong_ids, q$id)
}
}
wrong_str <- paste(wrong_ids, collapse = ", ")
res_data <- data.frame(
提交时间 = format(Sys.time(), "%Y-%m-%d %H:%M:%S"),
姓名 = input$student_name,
学号 = input$student_id,
得分 = score,
总题数 = length(questions),
错题题号 = wrong_str,
stringsAsFactors = FALSE
)
log_file <- "control_flow_quiz_results.csv"
if (!file.exists(log_file)) {
write.csv(res_data, log_file, row.names = FALSE, fileEncoding = "GBK")
} else {
write.table(
res_data, log_file,
sep = ",",
row.names = FALSE,
col.names = FALSE,
append = TRUE,
fileEncoding = "GBK"
)
}
submitted(TRUE)
showNotification("成绩已存档!", type = "message")
})
observeEvent(input$reset, {
session$reload()
})
output$result_ui <- renderUI({
if (!submitted()) return(NULL)
explanations <- data.frame(
题号 = sapply(questions, function(x) x$id),
状态 = sapply(questions, function(q) {
ans <- input[[paste0("q", q$id)]]
if (!is.null(ans) && as.numeric(ans) == q$correct) "✅ 正确" else "❌ 错误"
}),
正确答案 = sapply(questions, function(x) x$options[x$correct]),
解析 = sapply(questions, function(x) x$explanation),
stringsAsFactors = FALSE
)
output$explanation_table <- renderDT({
datatable(
explanations,
options = list(pageLength = 6, scrollX = TRUE),
rownames = FALSE
) %>%
formatStyle(
"状态",
color = styleEqual(c("✅ 正确", "❌ 错误"), c("green", "red"))
)
})
h3(
paste0("测试结束!得分:", sum(explanations$状态 == "✅ 正确"), " / ", length(questions)),
style = "text-align:center; color:#2c3e50;"
)
})
}
shinyApp(ui, server)