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

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: "alice@example.com" }

# 访问
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)     # => "alice@example.com"
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 动手练习

  1. 数组旋转
# 实现数组左旋转 k 位
# rotate_left([1, 2, 3, 4, 5], 2) => [3, 4, 5, 1, 2]
def rotate_left(arr, k)
  # 你的代码...
end
  1. 频率统计
# 统计字符串中每个字符出现的频率,返回按频率降序排列的数组
# char_frequency("hello") => [["l", 2], ["h", 1], ["e", 1], ["o", 1]]
def char_frequency(str)
  # 你的代码...
end
  1. 哈希扁平化
# 将嵌套哈希扁平化,用点号连接键
# 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+ 语法更简洁
迭代方法eachmapselectreduce 等核心方法
解构数组和块参数的解构赋值
嵌套使用 dig 安全访问嵌套结构
Set无序不重复集合,支持集合运算

📖 扩展阅读


上一章← 第 06 章:方法 下一章第 08 章:字符串与正则 →