Ruby 零基础教程

Ruby 哈希 (Hashes)

在 Ruby 中,哈希(Hashes,在其他编程语言中通常被称为字典或关联数组)是一种极其基础且强大的数据结构。它们允许你使用键值对 (Key-Value pairs) 来存储和检索数据,提供了一种极其灵活且高效的方式来组织和管理信息。

理解哈希对于任何 Ruby 开发者来说都至关重要。从存储应用程序的配置设置,到构建复杂的数据表示,哈希的应用无处不在。

1. 创建哈希

在 Ruby 中,有几种不同的方式可以创建哈希。

1.1 使用哈希字面量 (Hash Literals)

创建哈希最常见的方式是使用哈希字面量,即使用花括号 {} 将内容包裹起来。每个键和值之间用一个被称为“哈希火箭 (hash rocket)”的符号 => 分隔。

# 创建一个使用字符串作为键,整数作为值的哈希
person = {
  "name" => "Alice",
  "age" => 30,
  "city" => "New York"
}
puts person # 输出: {"name"=>"Alice", "age"=>30, "city"=>"New York"}

# 创建一个使用符号 (Symbol) 作为键,值类型混合的哈希
product = {
  :name => "Laptop",
  :price => 1200.00,
  :in_stock => true
}
puts product # 输出: {:name=>"Laptop", :price=>1200.0, :in_stock=>true}

1.2 使用 Hash.new 构造函数

你也可以使用 Hash.new 构造函数来创建一个空的哈希。

# 创建一个空哈希
empty_hash = Hash.new
puts empty_hash # 输出: {}

设置默认值:
使用 Hash.new 时,最强大的功能之一是可以为哈希中不存在的键设置一个默认值。这在统计或累加操作中非常有用,可以完美避免因为访问不存在的键而返回 nil 导致的报错。

# 创建一个带有默认值 0 的哈希
word_counts = Hash.new(0) # 如果查找的键不存在,它将默认返回 0

puts word_counts["the"] # 访问一个不存在的键,返回 0

word_counts["the"] += 1 # 相当于 word_counts["the"] = 0 + 1
puts word_counts["the"] # 输出: 1

puts word_counts["a"]   # 输出: 0 (键 "a" 依然不存在,返回默认值)

1.3 使用关键字参数语法 (符号键简写)

由于在 Ruby 中使用符号 (Symbol) 作为哈希的键是一种极其普遍的最佳实践,Ruby 提供了一种更简洁的语法(通常被称为关键字参数语法或 JSON 风格语法)。

# 使用简写语法创建以符号为键的哈希
book = {
  title: "The Ruby Programming Language", # 等同于 :title => "..."
  author: "David Flanagan",               # 等同于 :author => "..."
  year: 2008
}
puts book 
# 输出: {:title=>"The Ruby Programming Language", :author=>"David Flanagan", :year=>2008}
注意: 这种 key: value 的语法在底层完全等同于 :key => value,它只是提供了一种可读性更强、更现代的替代方案。

2. 访问哈希元素

你可以通过将键放入方括号 [] 中来访问哈希里对应的值。

2.1 基础访问

person = {
  "name" => "Alice",
  "age" => 30,
  "city" => "New York"
}

puts person["name"] # 输出: Alice
puts person["age"]  # 输出: 30

如果你尝试访问一个在哈希中不存在的键,Ruby 默认会返回 nil(除非你像前面提到的那样使用 Hash.new 设置了默认值)。

person = {
  "name" => "Alice",
  "age" => 30
}
puts person["address"] # 输出: nil (不报错,直接返回 nil)

2.2 使用 fetch 方法安全访问

fetch 方法提供了一种更优雅、更安全的处理缺失键的方式。

  • 如果键不存在,fetch 会抛出一个 KeyError 异常(这有助于尽早发现 Bug)。
  • 你也可以为 fetch 提供一个备用的默认值,如果键缺失,则返回该默认值。
person = {
  "name" => "Alice",
  "age" => 30
}

puts person.fetch("name") # 输出: Alice

# 下面这行代码会抛出 KeyError 异常,因为 "address" 键不存在
# puts person.fetch("address")

# 提供一个默认值 "Unknown"
puts person.fetch("address", "Unknown") # 输出: Unknown

3. 修改哈希

哈希是可变的(Mutable),这意味着你可以在创建哈希后自由地添加、修改和删除键值对。

3.1 添加和更新元素

要向哈希中添加新的键值对,只需使用 [] 操作符为新的键分配一个值即可。要更新现有键的值,使用完全相同的语法。

person = {
  "name" => "Alice",
  "age" => 30
}

# 添加一个新的键值对
person["city"] = "New York"
puts person # 输出: {"name"=>"Alice", "age"=>30, "city"=>"New York"}

# 更新现有键的值
person["age"] = 31
puts person # 输出: {"name"=>"Alice", "age"=>31, "city"=>"New York"}

3.2 移除元素

你可以使用 delete 方法从哈希中移除一个键值对。delete 方法会返回被删除键所对应的值。如果键没找到,则返回 nil

person = {
  "name" => "Alice",
  "age" => 30,
  "city" => "New York"
}

# 移除一个键值对
removed_value = person.delete("city")
puts removed_value # 输出: New York
puts person        # 输出: {"name"=>"Alice", "age"=>30}

# 尝试移除一个不存在的键
removed_value = person.delete("address")
puts removed_value # 输出: nil
puts person        # 输出: {"name"=>"Alice", "age"=>30}

4. 实战案例演示

让我们探索几个创建和访问哈希的实际应用场景。

4.1 案例 1:存储复杂实体信息

哈希非常适合用来表示具有多个属性的实体。注意,哈希的值也可以是数组或其他哈希。

student = {
  name: "Bob",
  student_id: "12345",
  courses: ["Math", "Science", "History"] # 值为一个数组
}

puts student[:name]       # 输出: Bob
puts student[:courses][0] # 输出: Math (先获取数组,再访问数组的第一个元素)

4.2 案例 2:应用程序配置管理

哈希通常用于存储应用程序或脚本的配置设置。

config = {
  database_host: "localhost",
  database_user: "admin",
  database_password: "password",
  port: 3000
}

puts "正在以用户 #{config[:database_user]} 连接到数据库 #{config[:database_host]}..."
# 输出: 正在以用户 admin 连接到数据库 localhost...

这种模式使得统一管理和访问配置参数变得非常容易。

4.3 案例 3:统计词频

哈希是解决计数类问题的完美工具。

text = "the quick brown fox jumps over the lazy dog"
words = text.split(" ") # 将文本分割成单词数组

# 创建一个默认值为 0 的哈希
word_counts = Hash.new(0) 

words.each do |word|
  word_counts[word] += 1 # 每次遇到一个单词,其对应的值就加 1
end

puts word_counts 
# 输出: {"the"=>2, "quick"=>1, "brown"=>1, "fox"=>1, "jumps"=>1, "over"=>1, "lazy"=>1, "dog"=>1}