Ruby 数组与哈希的遍历
1. 遍历数组
数组是元素的有序集合。Ruby 提供了多种极其优雅的方式来遍历它们。
1.1 each 方法
each 方法是遍历数组最基础也是最常用的方式。它会对数组中的每一个元素依次执行给定的代码块。
# 示例:使用 each 打印数组的每一个元素
numbers = [1, 2, 3, 4, 5]
numbers.each do |number|
puts number
end
# 输出:
# 1
# 2
# 3
# 4
# 5在这个例子中,each 方法遍历了 numbers 数组。对于数组中的每一个 number,代码块 do |number| ... end 都会被执行一次,而 puts number 语句将当前的数字打印到控制台。
进阶示例:在遍历期间修改元素(强烈警告)
虽然强烈不建议在使用 each 遍历数组时直接修改原数组(这通常会导致不可预知的行为),但为了演示,下面展示了一种做法:
# 示例:在遍历时修改数组(通常不推荐!)
numbers = [1, 2, 3, 4, 5]
new_numbers = [] # 推荐做法:创建一个新数组,防止修改原数组带来的问题
numbers.each do |number|
new_numbers << number * 2 # 将当前数字乘以 2 并追加到新数组中
end
puts new_numbers.inspect # => [2, 4, 6, 8, 10]
puts numbers.inspect # => [1, 2, 3, 4, 5]警告: 使用each时修改正在被遍历的数组,会导致如跳过元素或无限循环等不可预测的后果。更好的做法是像上面那样创建一个新数组,或者使用专门为转换数组设计的map方法。
1.2 each_with_index 方法
each_with_index 方法与 each 非常相似,但它不仅提供当前元素,还会同时提供该元素在数组中的索引 (index)。
# 示例:使用 each_with_index 打印每个元素及其索引
fruits = ["apple", "banana", "orange"]
fruits.each_with_index do |fruit, index|
puts "#{index + 1}: #{fruit}"
end
# 输出:
# 1: apple
# 2: banana
# 3: orange在这个例子中,each_with_index 遍历了 fruits 数组。对于每个 fruit 及其对应的 index,代码块被执行。为了更符合人类阅读习惯,我们在打印时将索引 index 加了 1。
进阶示例:使用 each_with_index 有条件地修改元素
# 示例:使用 each_with_index 有条件地修改元素
words = ["hello", "world", "ruby", "programming"]
words.each_with_index do |word, index|
words[index] = word.upcase if index.even? # 如果索引是偶数,则将其转换为大写
end
puts words.inspect # 输出: ["HELLO", "world", "RUBY", "programming"]1.3 map (或 collect) 方法
map 方法(其别名是 collect)会遍历数组,将代码块应用于每个元素,并返回一个包含所有代码块运行结果的全新数组。这是进行数组数据格式转换的神器。
# 示例:使用 map 创建一个包含数字翻倍的新数组
numbers = [1, 2, 3, 4, 5]
doubled_numbers = numbers.map do |number|
number * 2
end
puts doubled_numbers.inspect # 输出: [2, 4, 6, 8, 10]
puts numbers.inspect # 输出: [1, 2, 3, 4, 5] (原始数组保持不变)进阶示例:使用 map 转换由哈希组成的数组
假设我们有一个产品哈希的数组,并且我们想创建一个只包含所有“产品名称”的新数组:
products = [
{ name: "Laptop", price: 1200 },
{ name: "Keyboard", price: 75 },
{ name: "Mouse", price: 25 }
]
product_names = products.map do |product|
product[:name]
end
puts product_names.inspect # 输出: ["Laptop", "Keyboard", "Mouse"]1.4 select (或 filter) 方法
select 方法(别名为 filter)遍历数组并返回一个全新的数组,新数组中仅仅包含那些让代码块执行结果为 true 的元素。
# 示例:使用 select 从数组中过滤出偶数
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = numbers.select do |number|
number.even? # 每个值都会返回一个布尔值
end
puts even_numbers.inspect # 输出: [2, 4, 6]
puts numbers.inspect # 输出: [1, 2, 3, 4, 5, 6] (原始数组保持不变)进阶示例:使用 select 处理更复杂的条件
# 示例:使用 select 结合复杂条件
words = ["apple", "banana", "kiwi", "orange", "grape"]
long_words = words.select do |word|
word.length > 5 # 根据单词长度返回布尔值
end
puts long_words.inspect # 输出: ["banana", "orange"]1.5 reject 方法
reject 方法的作用正好与 select 相反。它遍历数组并返回一个全新的数组,新数组中仅包含那些让代码块执行结果为 false 的元素(即剔除符合条件的元素)。
# 示例:使用 reject 从数组中剔除偶数(保留奇数)
numbers = [1, 2, 3, 4, 5, 6]
odd_numbers = numbers.reject do |number|
number.even? # 与 select 的逻辑相反
end
puts odd_numbers.inspect # 输出: [1, 3, 5]
puts numbers.inspect # 输出: [1, 2, 3, 4, 5, 6] (原始数组保持不变)进阶示例:使用 reject 根据属性移除特定对象
# 示例:使用 reject 移除具有管理员权限的用户
users = [
{ name: "Alice", admin: true },
{ name: "Bob", admin: false },
{ name: "Charlie", admin: true }
]
non_admins = users.reject do |user|
user[:admin] # 当 user[:admin] 为真时,隐式返回 true,该用户被剔除
end
puts non_admins.inspect # 输出: [{:name=>"Bob", :admin=>false}]
puts users.inspect # 显示原始 users 数组保持不变1.6 find (或 detect) 方法
find 方法(别名为 detect)遍历数组,并返回第一个让代码块结果为 true 的元素。如果没有任何元素满足条件,则返回 nil。
# 示例:使用 find 在数组中查找第一个偶数
numbers = [1, 3, 2, 4, 5]
first_even = numbers.find do |number|
number.even? # 返回第一个使此条件为 true 的实例
end
puts first_even # 输出: 2
puts numbers.inspect # 输出: [1, 3, 2, 4, 5] (原始数组保持不变)进阶示例:在对象数组中根据条件查找特定对象
# 示例:使用 find 按名称定位特定产品
products = [
{ name: "Laptop", price: 1200 },
{ name: "Keyboard", price: 75 },
{ name: "Mouse", price: 25 }
]
keyboard = products.find do |product|
product[:name] == "Keyboard"
end
puts keyboard.inspect # 输出: {:name=>"Keyboard", :price=>75}1.7 find_all (或 select) 方法
请注意,find_all 完全等同于 select。这两个方法做的是同一件事。
2. 遍历哈希
哈希(字典或关联数组)以键值对的形式存储数据。Ruby 提供了几种非常便利的哈希遍历方式。
2.1 each 方法
当用于哈希时,each 方法会遍历每一对键值对。
# 示例:使用 each 打印哈希中的每个键值对
person = { name: "Alice", age: 30, city: "New York" }
person.each do |key, value|
puts "#{key}: #{value}"
end
# 输出:
# name: Alice
# age: 30
# city: New York进阶示例:基于键值对的条件输出
# 示例:结合条件逻辑使用 each
config = { timeout: 10, retries: 3, logging: true }
config.each do |key, value|
if value.is_a?(Numeric)
puts "#{key} 的值是一个数字: #{value}"
else
puts "#{key} 的值是: #{value}"
end
end2.2 each_key 方法
each_key 方法专门用于仅仅遍历哈希的键。
# 示例:使用 each_key 打印哈希中的每个键
person = { name: "Alice", age: 30, city: "New York" }
person.each_key do |key|
puts key
end
# 输出:
# name
# age
# city进阶示例:创建一个键被转换过的新哈希
# 示例:使用 each_key 转换哈希的键(创建一个新哈希)
data = { "first_name" => "John", "last_name" => "Doe" }
transformed_data = {}
data.each_key do |key|
new_key = key.gsub("_", " ").capitalize
transformed_data[new_key] = data[key]
end
puts transformed_data.inspect # 输出: {"First name"=>"John", "Last name"=>"Doe"}2.3 each_value 方法
each_value 方法专门用于仅仅遍历哈希的值。
# 示例:使用 each_value 打印哈希中的每个值
person = { name: "Alice", age: 30, city: "New York" }
person.each_value do |value|
puts value
end
# 输出:
# Alice
# 30
# New York进阶示例:通过哈希值计算统计数据
# 示例:使用 each_value 计算总分
student_scores = { alice: 85, bob: 92, charlie: 78 }
total_score = 0
student_scores.each_value do |score|
total_score += score
end
puts "总分: #{total_score}" # 输出: 总分: 2552.4 哈希中的 map 方法
你可以在哈希上使用 map,但极其关键的一点是:它会返回一个由代码块运行结果组成的数组(Array),而不是一个新的哈希。
# 示例:使用 map 将值提取到数组中
person = { name: "Alice", age: 30, city: "New York" }
values = person.map do |key, value|
value # 仅返回每个键值对中的值
end
puts values.inspect # 输出: ["Alice", 30, "New York"]进阶示例:将键值对转换为全新的字符串数组
# 示例:使用 map 创建一个格式化字符串数组
product = { name: "Book", price: 25, quantity: 2 }
details = product.map do |key, value|
"#{key.to_s.capitalize}: #{value}"
end
puts details.inspect # 输出: ["Name: Book", "Price: 25", "Quantity: 2"]2.5 哈希中的 select 和 reject 方法
select 和 reject 同样适用于哈希。与 map 不同,它们会返回一个全新的哈希,其中仅包含满足(或不满足,对于 reject 而言)代码块中指定条件的键值对。
# 示例:使用 select 根据值过滤键值对
ages = { alice: 30, bob: 25, charlie: 35 }
older_than_30 = ages.select do |name, age|
age > 30 # 仅保留值大于 30 的对
end
puts older_than_30.inspect # 输出: {:charlie=>35}
puts ages.inspect # 显示原始哈希保持不变# 示例:使用 reject 根据键过滤键值对
ages = { alice: 30, bob: 25, charlie: 35 }
not_alice = ages.reject do |name, age|
name == :alice # 移除属于 Alice 的键值对
end
puts not_alice.inspect # 输出: {:bob=>25, :charlie=>35}
puts ages.inspect # 显示原始哈希保持不变