Ruby Hashes
Hashes are another important data structure in Ruby, used to store key-value pair collections. Hashes are similar to arrays but use keys instead of indices to access elements. Ruby hashes are powerful and flexible, supporting multiple key types and rich operation methods. This chapter will introduce in detail the creation, manipulation, and processing methods of hashes in Ruby.
🎯 Hash Basics
Hash Definition
Ruby provides multiple ways to create hashes:
ruby
# Use literal syntax (Ruby 1.9+ recommended way)
person = {
name: "Zhang San",
age: 25,
city: "Beijing"
}
# Traditional way using => symbol
person = {
:name => "Zhang San",
:age => 25,
:city => "Beijing"
}
# Use strings as keys
person = {
"name" => "Zhang San",
"age" => 25,
"city" => "Beijing"
}
# Use Hash.new to create empty hash
empty_hash = Hash.new
default_hash = Hash.new("default") # Set default value
# Use Hash[] method to create
fruits = Hash["Apple", 5, "Banana", 3, "Orange", 8]
# Or use array pairs
pairs = [["Apple", 5], ["Banana", 3], ["Orange", 8]]
fruits = Hash[pairs]
# Use Hash.new block to set default
counter = Hash.new { |hash, key| hash[key] = 0 }
counter["Apple"] += 1
puts counter["Apple"] # 1
puts counter["Banana"] # 0 (auto initialized)Hash Access
ruby
person = {
name: "Zhang San",
age: 25,
city: "Beijing",
skills: ["Ruby", "Python", "JavaScript"]
}
# Access value by key
puts person[:name] # Zhang San
puts person[:age] # 25
puts person[:skills] # ["Ruby", "Python", "JavaScript"]
# Use string key to access
data = {
"name" => "Li Si",
"age" => 30
}
puts data["name"] # Li Si
# Use fetch method (safer)
puts person.fetch(:name) # Zhang San
puts person.fetch(:salary, 0) # 0 (default value)
# person.fetch(:salary) # Raises KeyError exception
# Use fetch with block to handle missing keys
salary = person.fetch(:salary) { |key| "#{key} not set" }
puts salary # salary not set
# Check if key exists
puts person.key?(:name) # true
puts person.key?(:salary) # false
puts person.has_key?(:name) # true (alias for key?)
puts person.value?("Zhang San") # true
puts person.has_value?("Zhang San") # true (alias for value?)📏 Hash Properties
Hash Size and Comparison
ruby
person = {
name: "Zhang San",
age: 25,
city: "Beijing"
}
# Get hash size
puts person.length # 3
puts person.size # 3 (alias for length)
puts person.count # 3
# Check if hash is empty
puts person.empty? # false
puts Hash.new.empty? # true
# Compare hashes
hash1 = {a: 1, b: 2}
hash2 = {a: 1, b: 2}
hash3 = {b: 2, a: 1} # Different order but same content
puts hash1 == hash2 # true
puts hash1 == hash3 # true (hash comparison doesn't consider key order)
puts hash1.eql?(hash2) # true
# Object identity comparison
puts hash1.equal?(hash2) # false
puts hash1.equal?(hash1) # true➕ Hash Operations
Adding and Modifying Key-Value Pairs
ruby
person = {
name: "Zhang San",
age: 25
}
# Add new key-value pair
person[:city] = "Beijing"
person[:skills] = ["Ruby", "Python"]
# Use store method
person.store(:email, "zhangsan@example.com")
# Batch update
person.merge!({
age: 26,
department: "Engineering"
})
puts person.inspect
# {:name=>"Zhang San", :age=>26, :city=>"Beijing", :skills=>["Ruby", "Python"], :email=>"zhangsan@example.com", :department=>"Engineering"}
# Use merge to create new hash without modifying original
new_person = person.merge({title: "Engineer"})
puts person.key?(:title) # false (original hash unchanged)
puts new_person.key?(:title) # true (new hash has title key)Deleting Key-Value Pairs
ruby
person = {
name: "Zhang San",
age: 25,
city: "Beijing",
salary: 10000,
department: "Engineering"
}
# Delete specified key
deleted_value = person.delete(:salary)
puts deleted_value # 10000
puts person.key?(:salary) # false
# Delete key-value pairs satisfying condition
person.delete_if { |key, value| key == :department }
puts person.key?(:department) # false
# Keep key-value pairs satisfying condition
filtered_person = person.keep_if { |key, value| key != :age }
puts person # {:name=>"Zhang San", :city=>"Beijing"} (original hash modified)
# Clear hash
person.clear
puts person.inspect # {}Update and Replace
ruby
# Use update method (alias for merge!)
original = {a: 1, b: 2}
additional = {b: 3, c: 4}
original.update(additional)
puts original # {:a=>1, :b=>3, :c=>4} (b's value updated)
# Use block to handle duplicate keys
hash1 = {a: 1, b: 2}
hash2 = {b: 3, c: 4}
result = hash1.merge(hash2) { |key, old_val, new_val| old_val + new_val }
puts result # {:a=>1, :b=>5, :c=>4} (b's values added)
# Conditional update
person = {name: "Zhang San", age: 25}
person[:age] = 26 if person[:age] < 30
puts person[:age] # 26🔍 Hash Search and Filter
Find Key-Value Pairs
ruby
scores = {
"Zhang San" => 85,
"Li Si" => 92,
"Wang Wu" => 78,
"Zhao Liu" => 96
}
# Find first key-value pair satisfying condition
high_scorer = scores.find { |name, score| score > 90 }
puts high_scorer # ["Li Si", 92]
# Find all key-value pairs satisfying condition
high_scorers = scores.select { |name, score| score > 85 }
puts high_scorers # {"Li Si"=>92, "Zhao Liu"=>96}
# Find key-value pairs not satisfying condition
low_scorers = scores.reject { |name, score| score > 85 }
puts low_scorers # {"Zhang San"=>85, "Wang Wu"=>78}
# Find keys or values
names_with_high_scores = scores.keys.select { |name| scores[name] > 85 }
puts names_with_high_scores # ["Li Si", "Zhao Liu"]
scores_above_threshold = scores.values.select { |score| score > 85 }
puts scores_above_threshold # [92, 96]Hash Filtering and Grouping
ruby
# Use slice to get sub-hash of specified keys
person = {
name: "Zhang San",
age: 25,
city: "Beijing",
salary: 10000,
department: "Engineering"
}
basic_info = person.slice(:name, :age, :city)
puts basic_info # {:name=>"Zhang San", :age=>25, :city=>"Beijing"}
# Use except to exclude specified keys
detailed_info = person.except(:salary, :department)
puts detailed_info # {:name=>"Zhang San", :age=>25, :city=>"Beijing"}
# Grouping
students = [
{name: "Zhang San", grade: "A", subject: "Math"},
{name: "Li Si", grade: "B", subject: "Math"},
{name: "Wang Wu", grade: "A", subject: "English"},
{name: "Zhao Liu", grade: "C", subject: "English"}
]
grouped_by_grade = students.group_by { |student| student[:grade] }
puts grouped_by_grade
# {"A"=>[{:name=>"Zhang San", :grade=>"A", :subject=>"Math"}, {:name=>"Wang Wu", :grade=>"A", :subject=>"English"}],
# "B"=>[{:name=>"Li Si", :grade=>"B", :subject=>"Math"}],
# "C"=>[{:name=>"Zhao Liu", :grade=>"C", :subject=>"English"}]}
# Group by key
data = {a1: 1, a2: 2, b1: 3, b2: 4}
grouped_by_prefix = data.group_by { |key, value| key.to_s[0] }
puts grouped_by_prefix
# {"a"=>[[:a1, 1], [:a2, 2]], "b"=>[[:b1, 3], [:b2, 4]]}🔧 Hash Transformation
Mapping and Conversion
ruby
prices = {
"Apple" => 5.0,
"Banana" => 3.0,
"Orange" => 4.0
}
# Transform keys
symbol_keys = prices.transform_keys { |key| key.to_sym }
puts symbol_keys # {:Apple=>5.0, :Banana=>3.0, :Orange=>4.0}
# Transform values
discount_prices = prices.transform_values { |price| price * 0.9 }
puts discount_prices # {"Apple"=>4.5, "Banana"=>2.7, "Orange"=>3.6}
# Transform both keys and values
formatted_data = prices.transform_keys(&:upcase).transform_values { |price| "$#{price}" }
puts formatted_data # {"Apple"=>"$5.0", "Banana"=>"$3.0", "Orange"=>"$4.0"}
# In-place transformation
prices.transform_keys! { |key| key.to_sym }
puts prices # {:Apple=>5.0, :Banana=>3.0, :Orange=>4.0}Sorting and Reversing
ruby
# Sort by key
data = {c: 3, a: 1, b: 2}
sorted_by_key = data.sort
puts sorted_by_key.inspect # [[:a, 1], [:b, 2], [:c, 3]]
sorted_hash = data.sort.to_h
puts sorted_hash # {:a=>1, :b=>2, :c=>3}
# Sort by value
sorted_by_value = data.sort_by { |key, value| value }
puts sorted_by_value.inspect # [[:a, 1], [:b, 2], [:c, 3]]
# Sort by key in descending order
desc_sorted = data.sort { |a, b| b[0] <=> a[0] }
puts desc_sorted.to_h # {:c=>3, :b=>2, :a=>1}
# Invert key-value pairs
inverted = data.invert
puts inverted # {1=>:a, 2=>:b, 3=>:c}
# Note: If values have duplicates, data will be lost after inversion🔁 Hash Iteration
Basic Iteration
ruby
person = {
name: "Zhang San",
age: 25,
city: "Beijing"
}
# Iterate key-value pairs
person.each { |key, value| puts "#{key}: #{value}" }
# name: Zhang San
# age: 25
# city: Beijing
# Iterate only keys
person.each_key { |key| puts "Key: #{key}" }
# Key: name
# Key: age
# Key: city
# Iterate only values
person.each_value { |value| puts "Value: #{value}" }
# Value: Zhang San
# Value: 25
# Value: Beijing
# Use each_pair (alias for each)
person.each_pair { |key, value| puts "#{key} => #{value}" }Advanced Iteration
ruby
scores = {
"Zhang San" => 85,
"Li Si" => 92,
"Wang Wu" => 78,
"Zhao Liu" => 96
}
# Use map to process key-value pairs
grade_messages = scores.map { |name, score| "#{name}'s score is #{score} points" }
puts grade_messages.inspect
# ["Zhang San's score is 85 points", "Li Si's score is 92 points", "Wang Wu's score is 78 points", "Zhao Liu's score is 96 points"]
# Use with_object to accumulate results
total_and_count = scores.each_with_object({sum: 0, count: 0}) do |(name, score), acc|
acc[:sum] += score
acc[:count] += 1
end
average = total_and_count[:sum].to_f / total_and_count[:count]
puts "Average score: #{average}" # Average score: 87.75
# Use reduce for accumulation
sum = scores.reduce(0) { |total, (name, score)| total + score }
puts "Total score: #{sum}" # Total score: 351🎯 Hash Practice Examples
Configuration Manager
ruby
class ConfigManager
def initialize(defaults = {})
@config = defaults.dup
end
# Get config value
def get(key, default = nil)
@config.key?(key) ? @config[key] : default
end
# Set config value
def set(key, value)
@config[key] = value
end
# Batch set config
def update(config_hash)
@config.merge!(config_hash)
end
# Delete config item
def remove(key)
@config.delete(key)
end
# Check if config item exists
def exists?(key)
@config.key?(key)
end
# Get all config items
def all
@config.dup
end
# Reset config
def reset(defaults = {})
@config = defaults.dup
end
# Deep merge config (handle nested hashes)
def deep_merge(other_hash)
deep_merge_hash(@config, other_hash)
end
private
def deep_merge_hash(hash1, hash2)
hash2.each do |key, value|
if hash1.key?(key) && hash1[key].is_a?(Hash) && value.is_a?(Hash)
hash1[key] = deep_merge_hash(hash1[key], value)
else
hash1[key] = value
end
end
hash1
end
end
# Use configuration manager
defaults = {
database: {
host: "localhost",
port: 5432,
username: "user"
},
logging: {
level: "info",
file: "app.log"
}
}
config = ConfigManager.new(defaults)
puts config.get(:database) # {:host=>"localhost", :port=>5432, :username=>"user"}
# Update config
config.update({
database: {
host: "production.db.com",
password: "secret"
},
cache: {
enabled: true,
ttl: 3600
}
})
puts config.get(:database)
# {:host=>"production.db.com", :port=>5432, :username=>"user", :password=>"secret"}Data Statistical Analyzer
ruby
class DataAnalyzer
def initialize(data)
@data = data
end
# Calculate frequency distribution
def frequency
freq = Hash.new(0)
@data.each { |item| freq[item] += 1 }
freq
end
# Calculate percentage distribution
def percentage
freq = frequency
total = @data.length
freq.transform_values { |count| (count.to_f / total * 100).round(2) }
end
# Find most common elements
def most_common(n = 1)
frequency.sort_by { |key, value| -value }.first(n).to_h
end
# Find least common elements
def least_common(n = 1)
frequency.sort_by { |key, value| value }.first(n).to_h
end
# Statistical analysis of numeric data
def numeric_stats
return {} unless @data.all? { |item| item.is_a?(Numeric) }
{
count: @data.length,
sum: @data.sum,
mean: @data.sum.to_f / @data.length,
min: @data.min,
max: @data.max,
range: @data.max - @data.min
}
end
# Grouped statistics
def group_by(&block)
@data.group_by(&block)
end
# Conditional filtering
def filter(&block)
@data.select(&block)
end
end
# Use data analyzer
# String data analysis
words = ["apple", "banana", "apple", "cherry", "banana", "apple", "date"]
analyzer = DataAnalyzer.new(words)
puts "Frequency distribution: #{analyzer.frequency}"
# Frequency distribution: {"apple"=>3, "banana"=>2, "cherry"=>1, "date"=>1}
puts "Percentage distribution: #{analyzer.percentage}"
# Percentage distribution: {"apple"=>42.86, "banana"=>28.57, "cherry"=>14.29, "date"=>14.29}
puts "Most common 3: #{analyzer.most_common(3)}"
# Most common 3: {"apple"=>3, "banana"=>2, "cherry"=>1}
# Numeric data analysis
scores = [85, 92, 78, 96, 88, 91, 83, 89]
score_analyzer = DataAnalyzer.new(scores)
puts "Numeric statistics: #{score_analyzer.numeric_stats}"
# Numeric statistics: {:count=>8, :sum=>702, :mean=>87.75, :min=>78, :max=>96, :range=>18}📚 Next Steps
After mastering Ruby hash operations, it is recommended to continue learning:
- Ruby Ranges - Learn range object usage
- Ruby Date & Time - Master date and time processing
- Ruby Iterators - Deep dive into iteration patterns
- Ruby File Processing and I/O - Learn file read/write operations
Continue your Ruby learning journey!