Ruby 入门指南 / 第 07 章:数组与哈希
第 07 章:数组与哈希
“数据结构决定算法,算法决定效率。”
7.1 数组(Array)
7.1.1 创建数组
# 数组字面量
arr = [1, 2, 3, 4, 5]
empty = []
mixed = [1, "hello", 3.14, true, nil, :symbol]
# Array.new
Array.new # => []
Array.new(5) # => [nil, nil, nil, nil, nil]
Array.new(5, 0) # => [0, 0, 0, 0, 0]
Array.new(5) { |i| i * 2 } # => [0, 2, 4, 6, 8]
# %w 和 %W(字符串数组)
%w[hello world foo] # => ["hello", "world", "foo"]
%W[hello #{1+1} world] # => ["hello", "2", "world"]
# 范围转数组
(1..5).to_a # => [1, 2, 3, 4, 5]
# 字符串分割
"hello world".split(" ") # => ["hello", "world"]
"a,b,c".split(",") # => ["a", "b", "c"]
7.1.2 访问元素
arr = ["a", "b", "c", "d", "e"]
# 下标访问
arr[0] # => "a"
arr[2] # => "c"
arr[-1] # => "e"(负索引从末尾开始)
arr[-2] # => "d"
# first 和 last
arr.first # => "a"
arr.last # => "e"
arr.first(2) # => ["a", "b"]
arr.last(2) # => ["d", "e"]
# 切片
arr[1..3] # => ["b", "c", "d"]
arr[1...3] # => ["b", "c"]
arr[1, 3] # => ["b", "c", "d"](从索引 1 开始取 3 个)
# at 方法
arr.at(0) # => "a"
# values_at(多个索引)
arr.values_at(0, 2, 4) # => ["a", "c", "e"]
# fetch(带错误处理)
arr.fetch(0) # => "a"
arr.fetch(100) # => IndexError
arr.fetch(100, "default") # => "default"
arr.fetch(100) { |i| "index #{i}" } # => "index 100"
7.1.3 添加和删除元素
arr = [1, 2, 3]
# 添加
arr.push(4) # => [1, 2, 3, 4](末尾添加)
arr << 5 # => [1, 2, 3, 4, 5](同 push,更常用)
arr.unshift(0) # => [0, 1, 2, 3, 4, 5](开头添加)
arr.insert(3, "x") # => [0, 1, 2, "x", 3, 4, 5](指定位置插入)
arr.concat([6, 7]) # => [0, 1, 2, "x", 3, 4, 5, 6, 7](连接数组)
# 删除
arr = [1, 2, 3, 4, 5]
arr.pop # => 5, arr = [1, 2, 3, 4](移除末尾)
arr.shift # => 1, arr = [2, 3, 4](移除开头)
arr.delete(3) # arr = [2, 4](按值删除)
arr.delete_at(0) # => 2, arr = [4](按索引删除)
# 拒绝/选择(返回新数组)
arr = [1, 2, 3, 4, 5]
arr.reject { |n| n > 3 } # => [1, 2, 3]
arr.select { |n| n > 3 } # => [4, 5]
# 原地修改版本
arr = [1, 2, 3, 4, 5]
arr.reject! { |n| n > 3 } # arr = [1, 2, 3]
# 清空
arr.clear # arr = []
# compact(移除 nil)
[1, nil, 2, nil, 3].compact # => [1, 2, 3]
# uniq(去重)
[1, 2, 2, 3, 3, 3].uniq # => [1, 2, 3]
# 删除满足条件的元素(delete_if)
arr = [1, 2, 3, 4, 5]
arr.delete_if { |n| n.even? } # => [1, 3, 5]
7.1.4 数组运算
a = [1, 2, 3, 4]
b = [3, 4, 5, 6]
# 并集
a | b # => [1, 2, 3, 4, 5, 6]
a.union(b) # => [1, 2, 3, 4, 5, 6](Ruby 2.7+)
# 交集
a & b # => [3, 4]
a.intersection(b) # => [3, 4](Ruby 2.7+)
# 差集
a - b # => [1, 2]
a.difference(b) # => [1, 2](Ruby 2.7+)
# 连接
a + b # => [1, 2, 3, 4, 3, 4, 5, 6]
a.concat(b) # 原地修改
# 乘法
[1, 2] * 3 # => [1, 2, 1, 2, 1, 2]
# 压缩(zip)
[1, 2, 3].zip(["a", "b", "c"]) # => [[1, "a"], [2, "b"], [3, "c"]]
# 笛卡尔积
[1, 2].product(["a", "b"])
# => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
7.1.5 数组变换
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# 排序
arr.sort # => [1, 1, 2, 3, 4, 5, 6, 9]
arr.sort.reverse # => [9, 6, 5, 4, 3, 2, 1, 1]
arr.sort_by { |n| -n } # => [9, 6, 5, 4, 3, 2, 1, 1]
# 打乱
arr.shuffle # => 随机顺序
arr.shuffle! # 原地打乱
# 翻转
arr.reverse # => [6, 2, 9, 5, 1, 4, 1, 3]
# 展平
[[1, 2], [3, [4, 5]]].flatten # => [1, 2, 3, 4, 5]
[[1, 2], [3, [4, 5]]].flatten(1) # => [1, 2, 3, [4, 5]](只展一层)
# map / collect
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
[1, 2, 3].map! { |n| n * 2 } # 原地修改
# flat_map
[1, 2, 3].flat_map { |n| [n, n * 2] } # => [1, 2, 2, 4, 3, 6]
# each_with_object
[1, 2, 3].each_with_object([]) { |n, arr| arr << n * 2 }
# => [2, 4, 6]
# 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+)
%w[a b a c b a].tally # => {"a"=>3, "b"=>2, "c"=>1}
# 聚合
[1, 2, 3, 4, 5].reduce(:+) # => 15
[1, 2, 3, 4, 5].reduce(0) { |s, n| s + n } # => 15
[1, 2, 3, 4, 5].sum # => 15
[1, 2, 3, 4, 5].min # => 1
[1, 2, 3, 4, 5].max # => 5
[1, 2, 3, 4, 5].minmax # => [1, 5]
7.1.6 常用查询方法
arr = [1, 2, 3, 4, 5]
arr.length # => 5(同 size、count)
arr.empty? # => false
[].empty? # => true
arr.include?(3) # => true
arr.any? { |n| n > 4 } # => true
arr.all? { |n| n > 0 } # => true
arr.none? { |n| n > 10 } # => true
arr.one? { |n| n == 3 } # => true
arr.count { |n| n.even? } # => 2
arr.sum # => 15
arr.min # => 1
arr.max # => 5
arr.index(3) # => 2(第一个匹配的索引)
arr.rindex(3) # => 2(最后一个匹配的索引)
arr.find { |n| n > 3 } # => 4
arr.bsearch { |n| n >= 3 } # => 3(二分查找,要求有序)
7.2 哈希(Hash)
7.2.1 创建哈希
# 字面量(Ruby 1.9+ 语法)
h = { name: "Alice", age: 25, city: "Beijing" }
# 旧语法
h = { :name => "Alice", :age => 25, :city => "Beijing" }
# 空哈希
h = {}
h = Hash.new
h = Hash.new(0) # 默认值为 0
# 从数组创建
Hash[ [[:name, "Alice"], [:age, 25]] ]
# => {name: "Alice", age: 25}
# to_h
[[:name, "Alice"], [:age, 25]].to_h
# => {name: "Alice", age: 25}
# each_with_object
%w[a b c].each_with_index.each_with_object({}) { |(v, i), h| h[v] = i }
# => {"a"=>0, "b"=>1, "c"=>2}
7.2.2 访问和修改
user = { name: "Alice", age: 25, email: "[email protected]" }
# 访问
user[:name] # => "Alice"
user[:phone] # => nil(键不存在)
user.fetch(:phone) # => KeyError
user.fetch(:phone, "N/A") # => "N/A"
user.fetch(:phone) { "N/A" } # => "N/A"
# 设置
user[:age] = 26
user[:city] = "Shanghai"
# 存在性检查
user.key?(:name) # => true
user.include?(:name) # => true
user.member?(:name) # => true
user.value?("Alice") # => true
# 删除
user.delete(:email) # => "[email protected]"
user.delete_if { |k, v| v.nil? }
# 默认值
h = Hash.new("default")
h[:missing] # => "default"
h = Hash.new { |hash, key| hash[key] = [] }
h[:items] << 1 # 自动创建数组
h[:items] << 2
h # => {items: [1, 2]}
7.2.3 哈希遍历
user = { name: "Alice", age: 25, city: "Beijing" }
# 遍历键值对
user.each do |key, value|
puts "#{key}: #{value}"
end
# 遍历键
user.each_key { |k| puts k }
# 遍历值
user.each_value { |v| puts v }
# 获取所有键和值
user.keys # => [:name, :age, :city]
user.values # => ["Alice", 25, "Beijing"]
user.to_a # => [[:name, "Alice"], [:age, 25], [:city, "Beijing"]]
7.2.4 哈希变换
user = { name: "Alice", age: 25, city: "Beijing" }
# map(返回数组)
user.map { |k, v| "#{k}=#{v}" }.join("&")
# => "name=Alice&age=25&city=Beijing"
# transform_keys(Ruby 2.5+)
user.transform_keys(&:to_s)
# => {"name"=>"Alice", "age"=>25, "city"=>"Beijing"}
# transform_values(Ruby 2.4+)
user.transform_values { |v| v.is_a?(String) ? v.upcase : v }
# => {name: "ALICE", age: 25, city: "BEIJING"}
# select / filter
user.select { |k, v| v.is_a?(String) }
# => {name: "Alice", city: "Beijing"}
# reject
user.reject { |k, v| k == :age }
# => {name: "Alice", city: "Beijing"}
# merge(合并)
defaults = { color: "red", size: "medium" }
custom = { color: "blue", weight: "100g" }
defaults.merge(custom)
# => {color: "blue", size: "medium", weight: "100g"}
# merge with block(冲突处理)
defaults.merge(custom) { |key, old_val, new_val| "#{old_val}/#{new_val}" }
# => {color: "red/blue", size: "medium", weight: "100g"}
# dig(安全嵌套访问,Ruby 2.3+)
data = { user: { profile: { name: "Alice" } } }
data.dig(:user, :profile, :name) # => "Alice"
data.dig(:user, :settings, :theme) # => nil(不报错)
# fetch_values(Ruby 2.3+)
user.fetch_values(:name, :age) # => ["Alice", 25]
# slice(Ruby 2.5+)
user.slice(:name, :age) # => {name: "Alice", age: 25}
# except(Ruby 3.0+)
user.except(:age) # => {name: "Alice", city: "Beijing"}
# compact(Ruby 2.4+,移除 nil 值)
{ a: 1, b: nil, c: 3 }.compact # => {a: 1, c: 3}
7.2.5 哈希与关键字参数
# Ruby 3.0+ 哈希和关键字参数分离
options = { host: "localhost", port: 3000 }
# ✅ 正确方式
connect(**options) # 展开哈希为关键字参数
connect(host: "localhost", port: 3000)
# ❌ Ruby 3.0 之前可以,3.0+ 报错
# connect(options) # ArgumentError
7.3 嵌套结构
7.3.1 嵌套数组
# 二维数组
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
matrix[0][0] # => 1
matrix[1][2] # => 6
# 遍历二维数组
matrix.each_with_index do |row, i|
row.each_with_index do |val, j|
puts "[#{i}][#{j}] = #{val}"
end
end
# 转置
matrix.transpose
# => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
7.3.2 嵌套哈希
data = {
user: {
name: "Alice",
profile: {
age: 25,
hobbies: ["reading", "coding"]
}
},
settings: {
theme: "dark",
language: "zh-CN"
}
}
# 访问嵌套值
data[:user][:profile][:age] # => 25
data[:user][:profile][:hobbies][0] # => "reading"
# 安全访问(dig)
data.dig(:user, :profile, :age) # => 25
data.dig(:user, :address, :city) # => nil(不报错)
# 深度合并
def deep_merge(hash1, hash2)
hash1.merge(hash2) do |key, old_val, new_val|
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
deep_merge(old_val, new_val)
else
new_val
end
end
end
defaults = { db: { host: "localhost", port: 5432 }, cache: { ttl: 3600 } }
custom = { db: { port: 5433 }, cache: { strategy: "redis" } }
deep_merge(defaults, custom)
# => {db: {host: "localhost", port: 5433}, cache: {ttl: 3600, strategy: "redis"}}
7.4 解构赋值
7.4.1 数组解构
# 基本解构
a, b, c = [1, 2, 3]
puts a # => 1
puts b # => 2
puts c # => 3
# 变量少于元素
a, b = [1, 2, 3]
puts a # => 1
puts b # => 2
# 变量多于元素
a, b, c = [1, 2]
puts c # => nil
# 剩余收集(splat)
first, *rest = [1, 2, 3, 4, 5]
puts first # => 1
puts rest # => [2, 3, 4, 5]
first, *middle, last = [1, 2, 3, 4, 5]
puts first # => 1
puts middle # => [2, 3, 4]
puts last # => 5
# 交换变量
a, b = 1, 2
a, b = b, a
puts a # => 2
puts b # => 1
# 在方法中解构
def process((name, age, *skills))
"#{name} (#{age}) - Skills: #{skills.join(', ')}"
end
process(["Alice", 25, "Ruby", "Python", "Go"])
# => "Alice (25) - Skills: Ruby, Python, Go"
7.4.2 哈希解构
# 哈希解构(Ruby 3.1+ 模式匹配)
user = { name: "Alice", age: 25, city: "Beijing" }
# 使用 dig 或 fetch 代替
name = user[:name]
age = user[:age]
# Ruby 3.1+ 模式匹配
case { name: "Alice", age: 25 }
in { name: String => name, age: 25.. }
puts "Found: #{name}"
end
7.4.3 块参数解构
# 块参数自动解构
pairs = [[:name, "Alice"], [:age, 25], [:city, "Beijing"]]
pairs.each do |key, value|
puts "#{key}: #{value}"
end
# 嵌套解构
data = [[1, [2, 3]], [4, [5, 6]]]
data.each do |a, (b, c)|
puts "#{a}: #{b}, #{c}"
end
# 1: 2, 3
# 4: 5, 6
7.5 Set(集合)
require "set"
# 创建集合
s1 = Set.new([1, 2, 3, 2, 1]) # => #<Set: {1, 2, 3}>
s2 = Set.new([3, 4, 5])
# 添加元素
s1.add(4)
s1 << 5
# 集合运算
s1 | s2 # 并集 => #<Set: {1, 2, 3, 4, 5}>
s1 & s2 # 交集 => #<Set: {3, 4, 5}>
s1 - s2 # 差集 => #<Set: {1, 2}>
s1 ^ s2 # 对称差 => #<Set: {1, 2, 4, 5}>
# 查询
s1.include?(3) # => true
s1.subset?(s2) # => false
s1.superset?(s2) # => false
s1.size # => 5
# 转换
s1.to_a # => [1, 2, 3, 4, 5]
s1.to_a.sort # => [1, 2, 3, 4, 5]
7.6 实际业务场景
7.6.1 数据处理管道
# 处理销售数据
sales = [
{ product: "iPhone", price: 6999, quantity: 5, region: "华东" },
{ product: "MacBook", price: 12999, quantity: 2, region: "华北" },
{ product: "iPhone", price: 6999, quantity: 3, region: "华南" },
{ product: "iPad", price: 3999, quantity: 8, region: "华东" },
{ product: "MacBook", price: 12999, quantity: 1, region: "华南" }
]
# 按产品汇总销售额
revenue_by_product = sales
.group_by { |s| s[:product] }
.transform_values { |items| items.sum { |s| s[:price] * s[:quantity] } }
puts revenue_by_product
# {"iPhone"=>62991, "MacBook"=>38997, "iPad"=>31992}
# 找出总销售额最高的产品
top_product = revenue_by_product.max_by { |_, revenue| revenue }
puts "最畅销产品: #{top_product[0]} (¥#{top_product[1]})"
7.6.2 配置管理
# 多层配置合并
def load_config
defaults = {
server: { host: "0.0.0.0", port: 8080 },
database: { adapter: "sqlite3", pool: 5 },
logging: { level: "info", output: "stdout" }
}
env_config = {
server: { port: 3000 },
database: { adapter: "postgresql", host: "localhost" }
}
deep_merge(defaults, env_config)
end
7.6.3 分组统计
# 用户行为分析
events = [
{ user: "alice", action: "login", time: "09:00" },
{ user: "bob", action: "login", time: "09:05" },
{ user: "alice", action: "purchase", time: "09:30" },
{ user: "alice", action: "logout", time: "10:00" },
{ user: "bob", action: "purchase", time: "10:15" }
]
# 按用户分组并统计操作次数
user_stats = events
.group_by { |e| e[:user] }
.transform_values do |user_events|
user_events.tally { |e| e[:action] }
end
.transform_values { |actions| actions.tally }
# 更简洁的版本
user_action_counts = events
.group_by { |e| [e[:user], e[:action]] }
.transform_values(&:count)
puts user_action_counts
# {["alice", "login"]=>1, ["bob", "login"]=>1, ["alice", "purchase"]=>1, ...}
7.7 动手练习
- 数组旋转
# 实现数组左旋转 k 位
# rotate_left([1, 2, 3, 4, 5], 2) => [3, 4, 5, 1, 2]
def rotate_left(arr, k)
# 你的代码...
end
- 频率统计
# 统计字符串中每个字符出现的频率,返回按频率降序排列的数组
# char_frequency("hello") => [["l", 2], ["h", 1], ["e", 1], ["o", 1]]
def char_frequency(str)
# 你的代码...
end
- 哈希扁平化
# 将嵌套哈希扁平化,用点号连接键
# flatten_hash({a: {b: 1, c: {d: 2}}}) => {"a.b"=>1, "a.c.d"=>2}
def flatten_hash(hash, prefix = "")
# 你的代码...
end
7.8 本章小结
| 要点 | 说明 |
|---|---|
| 数组 | 有序集合,支持索引访问、丰富的迭代方法 |
| 哈希 | 键值对集合,Ruby 1.9+ 语法更简洁 |
| 迭代方法 | each、map、select、reduce 等核心方法 |
| 解构 | 数组和块参数的解构赋值 |
| 嵌套 | 使用 dig 安全访问嵌套结构 |
| Set | 无序不重复集合,支持集合运算 |
📖 扩展阅读
上一章:← 第 06 章:方法 下一章:第 08 章:字符串与正则 →