Skip to content

Ruby 迭代器

迭代器是Ruby中一个强大而优雅的特性,它提供了一种遍历集合元素的标准化方式。Ruby的迭代器不仅包括内置的迭代方法,还支持自定义迭代器,使得代码更加简洁和表达力强。本章将详细介绍Ruby中迭代器的使用方法和最佳实践。

🎯 迭代器基础

什么是迭代器

迭代器是一种设计模式,它提供了一种访问集合元素的方法,而无需暴露集合的内部表示。在Ruby中,迭代器通常以块的形式实现,使得代码更加简洁和易读。

ruby
# 基本迭代器使用
[1, 2, 3, 4, 5].each { |n| puts n }
# 输出:
# 1
# 2
# 3
# 4
# 5

# 使用do...end语法
[1, 2, 3, 4, 5].each do |n|
  puts n
end

# 迭代器与块参数
fruits = ["苹果", "香蕉", "橙子"]
fruits.each_with_index do |fruit, index|
  puts "#{index + 1}. #{fruit}"
end
# 输出:
# 1. 苹果
# 2. 香蕉
# 3. 橙子

内置迭代器方法

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

# map/collect - 转换每个元素
squared = [1, 2, 3, 4].map { |n| n ** 2 }
puts squared.inspect  # [1, 4, 9, 16]

# select/find_all - 筛选满足条件的元素
evens = [1, 2, 3, 4, 5, 6].select(&:even?)
puts evens.inspect  # [2, 4, 6]

# reject - 排除满足条件的元素
odds = [1, 2, 3, 4, 5, 6].reject(&:even?)
puts odds.inspect  # [1, 3, 5]

# find/detect - 查找第一个满足条件的元素
first_even = [1, 3, 4, 5, 6].find(&:even?)
puts first_even  # 4

# find_all - 查找所有满足条件的元素(与select相同)
all_evens = [1, 2, 3, 4, 5, 6].find_all(&:even?)
puts all_evens.inspect  # [2, 4, 6]

🔁 常用迭代器方法

基本迭代方法

ruby
# each - 对每个元素执行操作
numbers = [1, 2, 3, 4, 5]
numbers.each { |n| puts "数字: #{n}" }

# each_with_index - 带索引的迭代
fruits = ["苹果", "香蕉", "橙子"]
fruits.each_with_index { |fruit, index| puts "#{index}: #{fruit}" }

# reverse_each - 反向迭代
[1, 2, 3].reverse_each { |n| puts n }
# 输出: 3, 2, 1

# each_slice - 分块迭代
(1..10).each_slice(3) { |slice| puts slice.inspect }
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]

# each_cons - 连续元素迭代
(1..5).each_cons(3) { |cons| puts cons.inspect }
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]

转换迭代器

ruby
# map/collect - 转换每个元素
numbers = [1, 2, 3, 4, 5]
squared = numbers.map { |n| n ** 2 }
puts squared.inspect  # [1, 4, 9, 16, 25]

# map! - 就地转换
numbers.map! { |n| n * 2 }
puts numbers.inspect  # [2, 4, 6, 8, 10]

# flat_map - 扁平化映射
nested = [[1, 2], [3, 4], [5, 6]]
flattened = nested.flat_map { |arr| arr }
puts flattened.inspect  # [1, 2, 3, 4, 5, 6]

# collect_concat - 与flat_map相同
result = nested.collect_concat { |arr| arr.map { |n| n * 2 } }
puts result.inspect  # [2, 4, 6, 8, 10, 12]

# zip - 合并多个数组
letters = ["a", "b", "c"]
numbers = [1, 2, 3]
combined = letters.zip(numbers)
puts combined.inspect  # [["a", 1], ["b", 2], ["c", 3]]

letters.zip(numbers) { |letter, number| puts "#{letter}: #{number}" }
# a: 1
# b: 2
# c: 3

筛选迭代器

ruby
# select/find_all - 选择满足条件的元素
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = numbers.select { |n| n.even? }
puts evens.inspect  # [2, 4, 6, 8, 10]

# reject - 排除满足条件的元素
odds = numbers.reject { |n| n.even? }
puts odds.inspect  # [1, 3, 5, 7, 9]

# grep - 使用正则表达式筛选
words = ["apple", "banana", "cherry", "date"]
a_words = words.grep(/^a/)
puts a_words.inspect  # ["apple"]

# grep_v - 排除匹配正则表达式的元素
non_a_words = words.grep_v(/^a/)
puts non_a_words.inspect  # ["banana", "cherry", "date"]

# take_while - 从开始取满足条件的元素
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
small_numbers = numbers.take_while { |n| n < 5 }
puts small_numbers.inspect  # [1, 2, 3, 4]

# drop_while - 跳过开始满足条件的元素
large_numbers = numbers.drop_while { |n| n < 5 }
puts large_numbers.inspect  # [5, 6, 7, 8, 9, 10]

查找迭代器

ruby
# find/detect - 查找第一个满足条件的元素
numbers = [1, 3, 5, 6, 7, 8]
first_even = numbers.find(&:even?)
puts first_even  # 6

# find_index - 查找第一个满足条件的元素索引
index = numbers.find_index(&:even?)
puts index  # 3

# any? - 检查是否有元素满足条件
has_even = numbers.any?(&:even?)
puts has_even  # true

# all? - 检查是否所有元素都满足条件
all_even = numbers.all?(&:even?)
puts all_even  # false

# none? - 检查是否没有元素满足条件
no_negative = numbers.none? { |n| n < 0 }
puts no_negative  # true

# one? - 检查是否只有一个元素满足条件
one_even = numbers.one?(&:even?)
puts one_even  # false

# include?/member? - 检查是否包含特定元素
has_five = numbers.include?(5)
puts has_five  # true

🔢 数值迭代器

数值范围迭代

ruby
# times - 从0开始迭代指定次数
5.times { |i| puts "第#{i + 1}次" }
# 第1次
# 第2次
# 第3次
# 第4次
# 第5次

# upto - 从当前数向上迭代到指定数
1.upto(5) { |n| puts n }

# downto - 从当前数向下迭代到指定数
5.downto(1) { |n| puts n }

# step - 按指定步长迭代
1.step(10, 2) { |n| puts n }  # 1, 3, 5, 7, 9

# step with block parameters
1.step(10, 2) { |n| puts "数字: #{n}" }

# 使用范围迭代
(1..5).each { |n| puts n }
('a'..'e').each { |char| puts char }

数值累积迭代

ruby
# reduce/inject - 累积操作
numbers = [1, 2, 3, 4, 5]

# 求和
sum = numbers.reduce(0) { |total, n| total + n }
puts sum  # 15

# 使用符号简写
sum = numbers.reduce(0, :+)
puts sum  # 15

# 求积
product = numbers.reduce(1) { |total, n| total * n }
puts product  # 120

# 找最大值
max = numbers.reduce { |max, n| n > max ? n : max }
puts max  # 5

# 字符串连接
words = ["Hello", "World", "Ruby"]
sentence = words.reduce("") { |result, word| result + " " + word }.strip
puts sentence  # Hello World Ruby

# 构建哈希
pairs = [["name", "张三"], ["age", "25"], ["city", "北京"]]
hash = pairs.reduce({}) do |result, pair|
  result[pair[0]] = pair[1]
  result
end
puts hash.inspect  # {"name"=>"张三", "age"=>"25", "city"=>"北京"}

🔄 高级迭代器

分组和分区

ruby
# group_by - 按条件分组
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grouped = numbers.group_by { |n| n.even? ? "偶数" : "奇数" }
puts grouped
# {"奇数"=>[1, 3, 5, 7, 9], "偶数"=>[2, 4, 6, 8, 10]}

# partition - 分区为两组
partitioned = numbers.partition(&:even?)
puts partitioned.inspect
# [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]

# chunk - 按连续相同条件分块
data = [1, 2, 4, 6, 7, 9, 10, 12]
chunks = data.chunk { |n| n.even? }.to_a
puts chunks.inspect
# [[true, [1]], [false, [2, 4, 6]], [true, [7]], [false, [9, 10, 12]]]

# slice_before - 在满足条件前切片
words = ["apple", "ant", "banana", "bee", "cherry", "cat"]
sliced = words.slice_before { |word| word.start_with?('a') }.to_a
puts sliced.inspect
# [["apple"], ["ant", "banana"], ["bee", "cherry"], ["cat"]]

# slice_after - 在满足条件后切片
sliced_after = words.slice_after { |word| word.end_with?('e') }.to_a
puts sliced_after.inspect
# [["apple", "ant"], ["banana", "bee", "cherry"], ["cat"]]

排序迭代器

ruby
# sort - 排序
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
sorted = numbers.sort
puts sorted.inspect  # [1, 1, 2, 3, 4, 5, 6, 9]

# sort_by - 按特定条件排序
words = ["apple", "banana", "cherry", "date"]
by_length = words.sort_by(&:length)
puts by_length.inspect  # ["date", "apple", "cherry", "banana"]

# reverse - 反转
reversed = numbers.reverse
puts reversed.inspect  # [6, 2, 9, 5, 1, 4, 1, 3]

# shuffle - 随机排列
shuffled = numbers.shuffle
puts shuffled.inspect  # 随机顺序

# sample - 随机采样
sampled = numbers.sample(3)
puts sampled.inspect  # 随机3个元素

🎯 自定义迭代器

创建自定义迭代器

ruby
class CustomCollection
  def initialize(items)
    @items = items
  end
  
  # 基本迭代器
  def each
    @items.each { |item| yield item }
  end
  
  # 带索引的迭代器
  def each_with_index
    @items.each_with_index { |item, index| yield item, index }
  end
  
  # 自定义映射迭代器
  def custom_map
    result = []
    each { |item| result << yield(item) }
    result
  end
  
  # 条件迭代器
  def each_if(&condition)
    each do |item|
      yield item if condition.call(item)
    end
  end
  
  # 反向迭代器
  def reverse_each
    (@items.length - 1).downto(0) do |i|
      yield @items[i]
    end
  end
end

# 使用自定义迭代器
collection = CustomCollection.new([1, 2, 3, 4, 5])

# 基本迭代
collection.each { |n| puts n }

# 带索引迭代
collection.each_with_index { |item, index| puts "#{index}: #{item}" }

# 自定义映射
squared = collection.custom_map { |n| n ** 2 }
puts squared.inspect  # [1, 4, 9, 16, 25]

# 条件迭代
collection.each_if { |n| n.even? } { |n| puts "偶数: #{n}" }

# 反向迭代
collection.reverse_each { |n| puts "反向: #{n}" }

Enumerable模块

ruby
class NumberSequence
  include Enumerable
  
  def initialize(start, finish)
    @start = start
    @finish = finish
  end
  
  # 实现each方法以获得Enumerable的所有功能
  def each
    (@start..@finish).each { |n| yield n }
  end
  
  # 可以覆盖特定方法以提高性能
  def size
    @finish - @start + 1
  end
end

# 使用包含Enumerable的类
sequence = NumberSequence.new(1, 10)

# 现在可以使用所有Enumerable方法
puts sequence.map { |n| n * 2 }.inspect  # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
puts sequence.select(&:even?).inspect    # [2, 4, 6, 8, 10]
puts sequence.find { |n| n > 5 }         # 6
puts sequence.all? { |n| n > 0 }         # true
puts sequence.any? { |n| n > 15 }        # false
puts sequence.count                      # 10

🎯 迭代器实践示例

数据处理管道

ruby
class DataPipeline
  def initialize(data)
    @data = data
  end
  
  # 链式方法调用
  def filter(&block)
    DataPipeline.new(@data.select(&block))
  end
  
  def map(&block)
    DataPipeline.new(@data.map(&block))
  end
  
  def sort(&block)
    DataPipeline.new(@data.sort(&block))
  end
  
  def take(n)
    DataPipeline.new(@data.take(n))
  end
  
  def group_by(&block)
    @data.group_by(&block)
  end
  
  def result
    @data
  end
end

# 使用数据处理管道
numbers = (1..20).to_a
result = DataPipeline.new(numbers)
  .filter { |n| n.even? }
  .map { |n| n ** 2 }
  .sort { |a, b| b <=> a }
  .take(5)
  .result

puts result.inspect  # [400, 256, 144, 64, 16]

文件行处理

ruby
class FileProcessor
  def self.process_lines(filename, &block)
    File.open(filename, 'r') do |file|
      file.each_line.with_index(1) do |line, line_number|
        yield line.chomp, line_number
      end
    end
  rescue Errno::ENOENT
    puts "文件 #{filename} 不存在"
  rescue => e
    puts "处理文件时出错: #{e.message}"
  end
  
  def self.filter_lines(filename, pattern)
    lines = []
    process_lines(filename) do |line, line_number|
      lines << { line: line, number: line_number } if line.match?(pattern)
    end
    lines
  end
  
  def self.transform_lines(filename, &transformer)
    transformed = []
    process_lines(filename) do |line, line_number|
      transformed << { original: line, transformed: transformer.call(line), number: line_number }
    end
    transformed
  end
end

# 使用文件处理器(假设存在test.txt文件)
# filtered = FileProcessor.filter_lines('test.txt', /ruby/i)
# transformed = FileProcessor.transform_lines('test.txt') { |line| line.upcase }

迭代器组合器

ruby
class IteratorCombinator
  # 组合多个迭代器操作
  def self.process(data, operations)
    result = data
    operations.each do |operation|
      case operation[:type]
      when :map
        result = result.map(&operation[:block])
      when :select
        result = result.select(&operation[:block])
      when :reject
        result = result.reject(&operation[:block])
      when :sort
        result = result.sort(&operation[:block])
      when :take
        result = result.take(operation[:count])
      end
    end
    result
  end
  
  # 创建操作链
  def self.operation_chain
    []
  end
  
  # 添加映射操作
  def self.add_map(chain, &block)
    chain << { type: :map, block: block }
  end
  
  # 添加筛选操作
  def self.add_select(chain, &block)
    chain << { type: :select, block: block }
  end
  
  # 添加排序操作
  def self.add_sort(chain, &block)
    chain << { type: :sort, block: block }
  end
end

# 使用迭代器组合器
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

operations = IteratorCombinator.operation_chain
IteratorCombinator.add_select(operations) { |n| n.even? }
IteratorCombinator.add_map(operations) { |n| n ** 2 }
IteratorCombinator.add_sort(operations) { |a, b| b <=> a }

result = IteratorCombinator.process(numbers, operations)
puts result.inspect  # [100, 64, 36, 16, 4]

📊 性能优化

Lazy迭代器

ruby
# 对于大数据集,使用lazy迭代器
large_range = 1..1_000_000

# 普通方式(会处理所有元素)
# result = large_range.select { |n| n.even? }.map { |n| n ** 2 }.first(10)

# 使用lazy延迟计算
result = large_range.lazy
  .select { |n| n.even? }
  .map { |n| n ** 2 }
  .first(10)

puts result.inspect  # [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

# 自定义lazy迭代器
class CustomLazyProcessor
  def initialize(data)
    @data = data
  end
  
  def lazy
    LazyWrapper.new(@data)
  end
end

class LazyWrapper
  def initialize(data)
    @data = data
  end
  
  def map(&block)
    LazyMap.new(self, block)
  end
  
  def select(&block)
    LazySelect.new(self, block)
  end
  
  def take(n)
    LazyTake.new(self, n)
  end
  
  def to_a
    @data.to_a
  end
end

class LazyMap < LazyWrapper
  def initialize(parent, block)
    @parent = parent
    @block = block
  end
  
  def to_a
    @parent.to_a.map(&@block)
  end
end

class LazySelect < LazyWrapper
  def initialize(parent, block)
    @parent = parent
    @block = block
  end
  
  def to_a
    @parent.to_a.select(&@block)
  end
end

class LazyTake < LazyWrapper
  def initialize(parent, n)
    @parent = parent
    @n = n
  end
  
  def to_a
    @parent.to_a.take(@n)
  end
end

迭代器性能优化

ruby
# 避免不必要的数组创建
# 低效方式
def inefficient_process(data)
  data.map { |x| x * 2 }
      .select { |x| x > 10 }
      .map { |x| x.to_s }
end

# 高效方式
def efficient_process(data)
  result = []
  data.each do |x|
    doubled = x * 2
    if doubled > 10
      result << doubled.to_s
    end
  end
  result
end

# 使用each_with_object累积结果
def process_with_object(data)
  data.each_with_object([]) do |x, result|
    doubled = x * 2
    result << doubled.to_s if doubled > 10
  end
end

# 批量处理大数据集
def batch_process(data, batch_size = 1000)
  data.each_slice(batch_size) do |batch|
    # 处理每个批次
    processed_batch = batch.map { |x| x * 2 }
    # 执行其他操作...
  end
end

🎯 迭代器最佳实践

1. 选择合适的迭代器方法

ruby
# 对于简单遍历,使用each
[1, 2, 3].each { |n| puts n }

# 对于转换,使用map
squared = [1, 2, 3].map { |n| n ** 2 }

# 对于筛选,使用select
evens = [1, 2, 3, 4, 5].select(&:even?)

# 对于检查条件,使用any?/all?
has_even = [1, 2, 3].any?(&:even?)  # true
all_positive = [1, 2, 3].all? { |n| n > 0 }  # true

# 对于累积操作,使用reduce
sum = [1, 2, 3, 4, 5].reduce(:+)

# 对于查找,使用find
first_even = [1, 3, 4, 5].find(&:even?)  # 4

2. 块参数的最佳实践

ruby
# 使用有意义的参数名
users.each { |user| puts user.name }

# 对于简单的单参数块,使用符号简写
names = users.map(&:name)
evens = numbers.select(&:even?)

# 对于多参数块,明确命名
users.each_with_index { |user, index| puts "#{index}: #{user.name}" }

# 避免在块中修改外部变量
# 不推荐
total = 0
numbers.each { |n| total += n }

# 推荐
total = numbers.reduce(0, :+)
# 或者
total = numbers.sum

3. 错误处理和边界情况

ruby
class SafeIterator
  # 安全的迭代处理
  def self.safe_each(collection, &block)
    return [] unless collection.respond_to?(:each)
    
    result = []
    collection.each do |item|
      begin
        result << yield(item) if block_given?
      rescue => e
        puts "处理元素时出错: #{e.message}"
        result << nil
      end
    end
    result
  end
  
  # 安全的映射
  def self.safe_map(collection, &block)
    return [] unless collection.respond_to?(:map) && block_given?
    
    collection.map do |item|
      begin
        yield(item)
      rescue => e
        puts "转换元素时出错: #{e.message}"
        item  # 返回原值
      end
    end
  end
  
  # 安全的筛选
  def self.safe_select(collection, &block)
    return [] unless collection.respond_to?(:select) && block_given?
    
    collection.select do |item|
      begin
        yield(item)
      rescue => e
        puts "筛选元素时出错: #{e.message}"
        false  # 默认不包含出错元素
      end
    end
  end
end

# 使用安全迭代器
data = [1, 2, "invalid", 4, 5]
squared = SafeIterator.safe_map(data) { |n| n ** 2 }
puts squared.inspect  # [1, 4, "invalid", 16, 25] (处理错误时返回原值)

4. 迭代器在实际应用中的使用

ruby
# 用户数据处理
class UserProcessor
  def initialize(users)
    @users = users
  end
  
  # 活跃用户
  def active_users
    @users.select(&:active?)
  end
  
  # 按年龄分组
  def group_by_age_group
    @users.group_by do |user|
      case user.age
      when 0..17 then "未成年"
      when 18..35 then "青年"
      when 36..60 then "中年"
      else "老年"
      end
    end
  end
  
  # 计算平均年龄
  def average_age
    active_users.map(&:age).reduce(0.0, :+) / active_users.size
  end
  
  # 查找VIP用户
  def vip_users
    @users.select { |user| user.level >= 5 }
  end
  
  # 用户名列表
  def usernames
    @users.map(&:username).sort
  end
end

# 订单处理
class OrderProcessor
  def initialize(orders)
    @orders = orders
  end
  
  # 按状态分组订单
  def group_by_status
    @orders.group_by(&:status)
  end
  
  # 计算总收入
  def total_revenue
    @orders.reduce(0) { |sum, order| sum + order.amount }
  end
  
  # 高价值订单
  def high_value_orders(threshold = 1000)
    @orders.select { |order| order.amount > threshold }
  end
  
  # 按月份统计订单
  def orders_by_month
    @orders.group_by { |order| order.created_at.strftime("%Y-%m") }
  end
  
  # 最近的订单
  def recent_orders(days = 30)
    cutoff_date = Date.today - days
    @orders.select { |order| order.created_at >= cutoff_date }
  end
end

📚 下一步学习

掌握了Ruby迭代器后,建议继续学习:

继续您的Ruby学习之旅吧!

本站内容仅供学习和研究使用。