1. 什么是迭代器?

迭代器是一种方法,通过调用它可以逐个处理集合中的元素。Ruby 的迭代器通常接受块(do ... end{ ... }),在块中定义对每个元素的操作。它们替代了传统语言中的 for 循环,使代码更优雅。

核心理念

  • 块驱动:迭代器通过 yield 将控制权交给块。
  • 集合操作:主要用于数组、哈希、范围等。

2. 常见迭代器

each

遍历集合中的每个元素。

  • 数组
  fruits = ["苹果", "香蕉", "橙子"]
  fruits.each do |fruit|
    puts "我喜欢 #{fruit}"
  end

输出:

  我喜欢 苹果
  我喜欢 香蕉
  我喜欢 橙子
  • 哈希
  person = { 姓名: "张三", 年龄: 25 }
  person.each do |key, value|
    puts "#{key}: #{value}"
  end

输出:

  姓名: 张三
  年龄: 25
  • 范围
  (1..3).each { |n| puts "第 #{n} 次" }

输出:

  第 1 次
  第 2 次
  第 3 次

each_with_index

遍历时带上索引。

tasks = ["写代码", "开会", "休息"]
tasks.each_with_index do |task, index|
  puts "#{index + 1}. #{task}"
end

输出:

1. 写代码
2. 开会
3. 休息

map / collect

对每个元素应用操作,返回新数组。

numbers = [1, 2, 3]
doubled = numbers.map do |n|
  n * 2
end
puts doubled  # [2, 4, 6]

单行写法:

squared = [1, 2, 3].map { |n| n ** 2 }
puts squared  # [1, 4, 9]

select / find_all

筛选符合条件的元素,返回新数组。

numbers = [1, 2, 3, 4, 5]
even = numbers.select { |n| n % 2 == 0 }
puts even  # [2, 4]

reject

排除符合条件的元素,返回新数组。

odds = numbers.reject { |n| n % 2 == 0 }
puts odds  # [1, 3, 5]

find / detect

返回第一个符合条件的元素。

first_even = numbers.find { |n| n % 2 == 0 }
puts first_even  # 2

reduce / inject

累积操作,返回单一结果。

sum = [1, 2, 3, 4].reduce(0) { |total, n| total + n }
puts sum  # 10

# 指定初始值可选
product = [1, 2, 3].reduce(:*)  # 使用符号简化
puts product  # 6

times

重复执行指定次数。

5.times do |i|
  puts "第 #{i + 1} 次问候:你好!"
end

输出:

第 1 次问候:你好!
第 2 次问候:你好!
...
第 5 次问候:你好!

3. 自定义迭代器

通过 yield 创建自定义迭代器。

示例

def repeat(n)
  n.times { |i| yield(i) }
end

repeat(3) do |count|
  puts "计数:#{count}"
end

输出:

计数:0
计数:1
计数:2

带条件的迭代器

def filter_numbers(numbers)
  numbers.each do |n|
    yield(n) if n > 0
  end
end

filter_numbers([-1, 2, 0, 3]) { |n| puts "正数:#{n}" }

输出:

正数:2
正数:3

4. 迭代器的返回值

  • each:返回原集合。
  • map:返回新数组。
  • select:返回筛选后的数组。
  • reduce:返回累积结果。

示例

numbers = [1, 2, 3]
result = numbers.each { |n| puts n }
puts result  # [1, 2, 3](返回原数组)

mapped = numbers.map { |n| n * 2 }
puts mapped  # [2, 4, 6]

5. 中文支持示例

遍历任务

任务 = ["写代码", "开会", "休息"]
任务.each_with_index do |task, i|
  puts "任务 #{i + 1}:#{task}"
end

输出:

任务 1:写代码
任务 2:开会
任务 3:休息

转换中文数据

names = ["张三", "李四", "王五"]
greetings = names.map { |name| "你好,#{name}!" }
puts greetings

输出:

你好,张三!
你好,李四!
你好,王五!

6. 实践案例

案例 1:成绩统计

scores = [85, 92, 78, 95]
average = scores.reduce(0, :+) / scores.length.to_f
highest = scores.max
puts "平均分:#{average},最高分:#{highest}"

输出:

平均分:87.5,最高分:95

案例 2:任务过滤

tasks = ["写代码", "开会", "休息", "测试"]
long_tasks = tasks.select { |task| task.length > 2 }
puts "长任务:#{long_tasks.join(', ')}"

输出:

长任务:写代码, 休息

案例 3:自定义计数器

def count_to(limit)
  (1..limit).each do |n|
    yield(n)
  end
end

count_to(3) { |n| puts "计数:#{n}" }

输出:

计数:1
计数:2
计数:3

7. 迭代器的控制流

break

跳出迭代:

[1, 2, 3, 4].each do |n|
  puts n
  break if n == 2
end

输出:

1
2

next

跳到下一次迭代:

[1, 2, 3].each do |n|
  next if n % 2 == 0
  puts "奇数:#{n}"
end

输出:

奇数:1
奇数:3

redo

重复当前迭代:

count = 0
[1, 2].each do |n|
  count += 1
  puts "当前:#{n}"
  redo if count == 1
end

输出:

当前:1
当前:1
当前:2

8. 注意事项

  • 性能:迭代器是惰性计算的,适合大数据处理。
  • 返回值:注意迭代器的返回值是否符合需求(例如 each vs map)。
  • 块参数:参数数量需与迭代器传递的值匹配。

9. 高级用法

链式调用

numbers = [1, 2, 3, 4, 5]
result = numbers
  .select { |n| n % 2 == 0 }
  .map { |n| n * 10 }
puts result  # [20, 40]

自定义对象迭代

class TaskList
  def initialize(tasks)
    @tasks = tasks
  end

  def each
    @tasks.each { |task| yield(task) }
  end
end

list = TaskList.new(["写代码", "休息"])
list.each { |task| puts "任务:#{task}" }

输出:

任务:写代码
任务:休息

下一步

  • 练习:告诉我你想用迭代器做什么(遍历、转换等),我可以设计一个例子。
  • 问题解答:对迭代器用法有疑问吗?直接问我!
  • 深入学习:想了解迭代器与 Enumerator 或 Lazy 迭代吗?我可以继续讲解。

你现在想做什么?写代码、问问题,还是其他?