强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Ruby 入门指南 / 第 05 章:控制流程

第 05 章:控制流程

“程序 = 数据结构 + 算法。” —— Niklaus Wirth


5.1 条件判断

5.1.1 if / elsif / else

# if 语法
score = 85

if score >= 90
  puts "优秀"
elsif score >= 80
  puts "良好"
elsif score >= 60
  puts "及格"
else
  puts "不及格"
end

# if 作为表达式(返回值)
grade = if score >= 90
          "A"
        elsif score >= 80
          "B"
        elsif score >= 60
          "C"
        else
          "F"
        end
puts grade  # => "B"

5.1.2 三元运算符

age = 20
status = age >= 18 ? "成年人" : "未成年"
puts status  # => "成年人"

# 嵌套(不推荐,影响可读性)
grade = score >= 90 ? "A" : score >= 80 ? "B" : "C"

5.1.3 unless(除非)

# unless 是 if 的反义
unless user.admin?
  puts "你没有权限"
end

# 等价于
if !user.admin?
  puts "你没有权限"
end

# unless + else(不推荐,影响可读性)
unless success?
  puts "失败"
else
  puts "成功"
end

# unless 作为后置条件
puts "没有权限" unless user.admin?

5.1.4 case / when

# 基本 case 语句
day = "Monday"

case day
when "Monday"
  puts "星期一,加油!"
when "Tuesday", "Wednesday", "Thursday"
  puts "工作日"
when "Friday"
  puts "快到周末了!"
when "Saturday", "Sunday"
  puts "周末愉快!"
else
  puts "无效日期"
end

# case 返回值
result = case day
         when "Monday"    then "周一"
         when "Tuesday"   then "周二"
         when "Wednesday" then "周三"
         else "其他"
         end

# case 与范围
score = 85
grade = case score
        when 90..100 then "A"
        when 80...90 then "B"
        when 70...80 then "C"
        when 60...70 then "D"
        else "F"
        end

# case 与正则表达式
url = "https://example.com"
case url
when /https/
  puts "安全连接"
when /http/
  puts "普通连接"
when /ftp/
  puts "FTP 连接"
end

# case 与类型
value = 42
case value
when Integer  then puts "整数: #{value}"
when String   then puts "字符串: #{value}"
when Array    then puts "数组: #{value}"
when Float    then puts "浮点数: #{value}"
end

# 自定义 case 匹配(实现 === 方法)
class Temperature
  def initialize(value)
    @value = value
  end

  def ===(other)
    case other
    when Range
      other.cover?(@value)
    when Numeric
      @value == other
    else
      false
    end
  end
end

temp = Temperature.new(37.5)
case temp
when 36..37 then puts "正常体温"
when 37..38 then puts "低热"
when 38..39 then puts "中热"
else puts "需要就医"
end

5.1.5 条件修饰符(后置条件)

# 单行条件
puts "正数" if x > 0
puts "偶数" if n.even?
return unless valid?

# 优雅的守卫条件
def process(data)
  return nil if data.nil?
  return [] if data.empty?
  
  # 处理数据
  data.map(&:upcase)
end

5.1.6 条件判断最佳实践

场景推荐避免
简单二选一if/else? :嵌套三元
多分支case/when多层 elsif
守卫条件后置 if/unless多层嵌套
空值检查value.nil?value == nil
布尔判断if valueif value == true

5.2 循环结构

5.2.1 while 循环

# 基本 while
count = 0
while count < 5
  puts "Count: #{count}"
  count += 1
end

# 后置 while(至少执行一次)
count = 0
begin
  puts "Count: #{count}"
  count += 1
end while count < 5

# while 作为表达式
count = 0
result = while count < 3
           count += 1
         end
puts result  # => nil

5.2.2 until 循环

# until 是 while 的反义
count = 0
until count >= 5
  puts "Count: #{count}"
  count += 1
end

# 后置 until
count = 0
begin
  puts "Count: #{count}"
  count += 1
end until count >= 5

5.2.3 for 循环

# for...in(不常用,推荐使用迭代器)
for i in 1..5
  puts i
end

# 带索引
for i, v in ["a", "b", "c"].each_with_index
  puts "#{i}: #{v}"
end

# ⚠️ 注意:for 循环不会创建新的作用域
x = 10
for x in 1..3
  puts x
end
puts x  # => 3(x 被修改了!)

5.2.4 loop 循环

# loop 是无限循环,需要 break 退出
count = 0
loop do
  puts "Count: #{count}"
  count += 1
  break if count >= 5
end

# loop 返回值
result = loop do
  break "done" if condition
end
puts result  # => "done"

5.2.5 循环控制

# break - 退出循环
loop do
  input = gets.chomp
  break if input == "quit"
  puts "You said: #{input}"
end

# next - 跳过当前迭代
(1..10).each do |i|
  next if i.even?
  puts i  # 只打印奇数
end

# redo - 重试当前迭代
count = 0
[1, 2, 3].each do |i|
  count += 1
  redo if count < 3 && i == 1
  puts "#{i} (count: #{count})"
end

# retry - 重试整个迭代器(谨慎使用)
begin
  [1, 2, 3].each do |i|
    raise "error" if i == 2
    puts i
  end
rescue
  puts "caught error"
end

5.3 迭代器模式

5.3.1 Ruby 的迭代器哲学

# Ruby 不鼓励使用传统的 for 循环
# 而是使用迭代器(Iterator)

# ❌ 不推荐
for i in 0...array.length
  puts array[i]
end

# ✅ 推荐
array.each { |item| puts item }

# ❌ 不推荐
i = 0
while i < array.length
  puts array[i]
  i += 1
end

# ✅ 推荐
array.each { |item| puts item }

5.3.2 核心迭代器

# each - 最基本的迭代器
[1, 2, 3].each { |n| puts n }

# each_with_index - 带索引
["a", "b", "c"].each_with_index do |item, index|
  puts "#{index}: #{item}"
end

# map / collect - 转换
[1, 2, 3].map { |n| n * 2 }      # => [2, 4, 6]
[1, 2, 3].map(&:to_s)             # => ["1", "2", "3"]

# select / find_all - 过滤
[1, 2, 3, 4, 5].select(&:even?)  # => [2, 4]

# reject - 反向过滤
[1, 2, 3, 4, 5].reject(&:even?)  # => [1, 3, 5]

# find / detect - 查找第一个
[1, 2, 3, 4, 5].find { |n| n > 3 }  # => 4

# reduce / inject - 聚合
[1, 2, 3, 4, 5].reduce(0) { |sum, n| sum + n }  # => 15
[1, 2, 3, 4, 5].reduce(:+)  # => 15

# all? / any? / none? / one? - 谓词
[2, 4, 6].all?(&:even?)    # => true
[1, 2, 3].any?(&:even?)    # => true
[1, 3, 5].none?(&:even?)   # => true
[1, 2, 3].one? { |n| n == 2 }  # => true

# count - 计数
[1, 2, 3, 4, 5].count(&:even?)  # => 2

# min / max / minmax
[3, 1, 4, 1, 5].min        # => 1
[3, 1, 4, 1, 5].max        # => 5
[3, 1, 4, 1, 5].minmax     # => [1, 5]

# sort / sort_by
[3, 1, 4, 1, 5].sort                    # => [1, 1, 3, 4, 5]
["banana", "apple"].sort_by(&:length)    # => ["apple", "banana"]

# flat_map - 嵌套展平
[[1, 2], [3, 4]].flat_map { |a| a.map { |n| n * 2 } }
# => [2, 4, 6, 8]

# group_by - 分组
[1, 2, 3, 4, 5].group_by { |n| n.even? ? "even" : "odd" }
# => {"odd"=>[1, 3, 5], "even"=>[2, 4]}

# tally - 计数(Ruby 2.7+)
["a", "b", "a", "c", "b", "a"].tally
# => {"a"=>3, "b"=>2, "c"=>1}

5.3.3 链式迭代

# 链式调用让代码更简洁
result = (1..100)
  .select(&:odd?)            # 过滤奇数
  .map { |n| n ** 2 }        # 求平方
  .select { |n| n > 100 }    # 大于 100
  .first(5)                   # 取前 5 个
  .reduce(:+)                 # 求和
puts result

# 业务场景:处理用户数据
users = [
  { name: "Alice", age: 25, active: true },
  { name: "Bob", age: 17, active: true },
  { name: "Charlie", age: 30, active: false },
  { name: "David", age: 22, active: true }
]

adult_active_names = users
  .select { |u| u[:active] }
  .select { |u| u[:age] >= 18 }
  .map { |u| u[:name] }
  .sort
puts adult_active_names  # => ["Alice", "David"]

5.4 块(Block)

5.4.1 块的定义

# 块是 Ruby 最独特的特性之一
# 块可以是 { } 或 do...end

# 单行块用 { }
[1, 2, 3].each { |n| puts n }

# 多行块用 do...end
[1, 2, 3].each do |n|
  result = n * 2
  puts "#{n} * 2 = #{result}"
end

# 块不是对象,不能单独存在
# { puts "hello" }  # SyntaxError

# 块是方法的隐式参数
def greet
  puts "Before block"
  yield               # 调用块
  puts "After block"
end

greet { puts "Inside block" }
# 输出:
# Before block
# Inside block
# After block

5.4.2 yield 机制

# yield 调用传入的块
def say_hello
  puts "Hello!"
  yield
  puts "Goodbye!"
end

say_hello { puts "Block called!" }
# Hello!
# Block called!
# Goodbye!

# yield 传递参数给块
def repeat(times)
  times.times do |i|
    yield i
  end
end

repeat(3) { |i| puts "Iteration #{i}" }

# yield 返回块的返回值
def calculate
  result = yield(10, 20)
  puts "Result: #{result}"
end

calculate { |a, b| a + b }  # => Result: 30

# 检查是否有块传入
def optional_block
  if block_given?
    yield
  else
    "No block given"
  end
end

optional_block                  # => "No block given"
optional_block { "Block!" }     # => "Block!"

5.4.3 块作为闭包

# 块捕获定义时的上下文(闭包)
def create_multiplier(factor)
  -> (n) { n * factor }  # factor 被捕获
end

double = create_multiplier(2)
triple = create_multiplier(3)

double.call(5)  # => 10
triple.call(5)  # => 15

# 块捕获局部变量
x = 10
[1, 2, 3].each do |n|
  x += n  # 块可以修改外部变量
end
puts x  # => 16

5.5 高级迭代技巧

5.5.1 each_with_object

# each_with_object - 传递可变对象
result = [1, 2, 3, 4, 5].each_with_object({}) do |n, hash|
  hash[n] = n ** 2
end
puts result  # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}

# 业务场景:统计词频
words = %w[hello world hello ruby world hello]
freq = words.each_with_object(Hash.new(0)) do |word, hash|
  hash[word] += 1
end
puts freq  # => {"hello"=>3, "world"=>2, "ruby"=>1}

5.5.2 slice_when / chunk

# slice_when - 按条件切片
data = [1, 2, 4, 5, 7, 8, 10]
slices = data.slice_when { |a, b| b - a > 1 }.to_a
puts slices.inspect  # => [[1, 2], [4, 5], [7, 8], [10]]

# chunk - 按条件分块
data = [1, 1, 2, 2, 3, 3, 1, 1]
chunks = data.chunk { |n| n }.to_a
puts chunks.inspect  # => [[1, [1, 1]], [2, [2, 2]], [3, [3, 3]], [1, [1, 1]]]

5.5.3 each_cons / each_slice

# each_cons - 滑动窗口
[1, 2, 3, 4, 5].each_cons(2) do |pair|
  puts pair.inspect
end
# [1, 2]
# [2, 3]
# [3, 4]
# [4, 5]

# each_slice - 分批处理
(1..10).each_slice(3) do |batch|
  puts batch.inspect
end
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]

# 业务场景:分页处理
def process_in_batches(records, batch_size = 100)
  records.each_slice(batch_size).with_index do |batch, index|
    puts "Processing batch #{index + 1} (#{batch.size} records)"
    yield batch
  end
end

5.5.4 Enumerator 和 Lazy

# Enumerator - 惰性枚举
enum = [1, 2, 3].each
puts enum.next  # => 1
puts enum.next  # => 2
puts enum.next  # => 3

# Lazy - 惰性求值(处理大序列时有用)
# 不使用 Lazy(会创建中间数组)
(1..Float::INFINITY).select(&:even?).first(5)  # 死循环!

# 使用 Lazy
(1..Float::INFINITY).lazy.select(&:even?).first(5)  # => [2, 4, 6, 8, 10]

# Lazy 链式操作
result = (1..Float::INFINITY)
  .lazy
  .select(&:odd?)
  .map { |n| n ** 2 }
  .select { |n| n > 100 }
  .first(5)
puts result.inspect  # => [121, 169, 225, 289, 361]

5.6 异常处理中的控制流

5.6.1 begin / rescue / retry

# 重试机制
attempts = 0
begin
  attempts += 1
  puts "Attempt #{attempts}"
  # 模拟可能失败的操作
  raise "Network error" if attempts < 3
  puts "Success!"
rescue => e
  puts "Error: #{e.message}"
  retry if attempts < 3
  puts "Failed after #{attempts} attempts"
end

5.7 实际业务场景

5.7.1 数据验证

def validate_user(params)
  errors = []

  unless params[:name]&.length&.between?(2, 50)
    errors << "名字长度必须在 2-50 之间"
  end

  unless params[:email]&.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
    errors << "邮箱格式无效"
  end

  unless params[:age].is_a?(Integer) && params[:age].between?(0, 150)
    errors << "年龄必须是 0-150 之间的整数"
  end

  if errors.empty?
    { success: true }
  else
    { success: false, errors: errors }
  end
end

5.7.2 状态机

class Order
  STATES = %i[pending paid shipped delivered cancelled].freeze

  def initialize
    @state = :pending
  end

  def transition_to(new_state)
    case [@state, new_state]
    when [:pending, :paid]       then proceed(new_state)
    when [:paid, :shipped]       then proceed(new_state)
    when [:shipped, :delivered]  then proceed(new_state)
    when [:pending, :cancelled]  then proceed(new_state)
    when [:paid, :cancelled]     then proceed(new_state)
    else
      raise "Invalid transition: #{@state} -> #{new_state}"
    end
  end

  private

  def proceed(new_state)
    @state = new_state
    puts "Order transitioned to: #{new_state}"
  end
end

5.8 动手练习

  1. FizzBuzz:经典练习
# 打印 1-100,3 的倍数打印 Fizz,5 的倍数打印 Buzz,15 的倍数打印 FizzBuzz
(1..100).each do |n|
  # 你的代码...
end
  1. 嵌套迭代:乘法表
# 打印 9x9 乘法表
# 你的代码...
  1. 数据转换:处理嵌套数组
data = [[1, 2], [3, 4], [5, 6]]
# 将嵌套数组展平并求偶数之和
# 你的代码...

5.9 本章小结

要点说明
条件if/elsif/elseunlesscase/when、三元运算符
循环whileuntilloopfor...in(少用)
迭代器eachmapselectreduce 等链式操作
{ }do...end,通过 yield 调用
闭包块捕获定义时的上下文变量
惰性Lazy 枚举器处理无限序列

📖 扩展阅读


上一章← 第 04 章:变量与数据类型 下一章第 06 章:方法 →