Ruby 数组与哈希的全面解析
1. 数组操作
数组是 Ruby 中常用的数据结构,提供了丰富的操作方法。
1.1 元素提取
可以使用不同的索引方式从数组中提取元素,具体示例如下:
a = ['a', 'b', 'c', 'd']
a[0,2] # => ['a','b']: 从索引 0 开始取 2 个元素
a[0..2] # => ['a','b','c']: 取索引范围在 0 到 2 的元素
a[0...2] # => ['a','b']: 三个点表示不包含结束索引
a[1,1] # => ['b']: 从索引 1 开始取 1 个元素
a[-2,2] # => ['c','d']: 取最后两个元素
a[4,2] # => []: 索引超出范围,返回空数组
a[5,1] # => nil: 超出范围,无元素
a.slice(0..1) # => ['a','b']: slice 方法与 [] 功能相同
a.first(3) # => ['a','b','c']: 取前三个元素
a.last(1) # => ['d']: 取最后一个元素
还可以使用
values_at
方法提取任意位置的元素:
a.values_at(0,2) # => ['a','c']
a.values_at(4, 3, 2, 1) # => [nil, 'd','c','b']
a.values_at(0, 2..3, -1) # => ['a','c','d','d']
a.values_at(0..2,1..3) # => ['a','b','c','b','c','d']
1.2 元素修改
可以对数组元素进行修改、插入、删除和替换操作,示例代码如下:
# 修改元素值
a = [1,2,3]
a[0] = 0 # a 变为 [0,2,3]
a[-1] = 4 # a 变为 [0,2,4]
a[1] = nil # a 变为 [0,nil,4]
# 追加元素
a = [1,2,3]
a[3] = 4 # a 变为 [1,2,3,4]
a[5] = 6 # a 变为 [1,2,3,4,nil,6]
a << 7 # => [1,2,3,4,nil,6,7]
a << 8 << 9 # => [1,2,3,4,nil,6,7,8,9]
# 插入元素
a = ['a', 'b', 'c']
a.insert(1, 1, 2) # a 变为 ['a',1,2,'b','c']
# 删除元素
a = [1,2,3,4,5,6]
a.delete_at(4) # => 5: a 变为 [1,2,3,4,6]
a.delete_at(-1) # => 6: a 变为 [1,2,3,4]
a.delete_at(4) # => nil: a 不变
a.delete(4) # => 4: a 变为 [1,2,3]
a[1] = 1 # a 变为 [1,1,3]
a.delete(1) # => 1: a 变为 [3]
a = [1,2,3]
a.delete_if {|x| x%2==1} # a 变为 [2]
a.reject! {|x| x%2==0} # a 变为 []
# 移除元素和子数组
a = [1,2,3,4,5,6,7,8]
a.slice!(0) # => 1: a 变为 [2,3,4,5,6,7,8]
a.slice!(-1,1) # => [8]: a 变为 [2,3,4,5,6,7]
a.slice!(2..3) # => [4,5]: a 变为 [2,3,6,7]
a.slice!(4,2) # => []: a 不变
a.slice!(5,2) # => nil: a 变为 [2,3,6,7,nil]
# 替换子数组
a = ('a'..'e').to_a # => ['a','b','c','d','e']
a[0,2] = ['A','B'] # a 变为 ['A', 'B', 'c', 'd', 'e']
a[2...5]=['C','D','E'] # a 变为 ['A', 'B', 'C', 'D', 'E']
a[0,0] = [1,2,3] # 在开头插入元素
a[0..2] = [] # 删除元素
a[-1,1] = ['Z'] # 替换最后一个元素
a[-1,1] = 'Z' # 单元素替换,数组可选
a[1,4] = nil # Ruby 1.9: a 变为 ['A',nil]; Ruby 1.8: a 变为 ['A']
其他修改方法:
a = [4,5]
a.replace([1,2,3]) # a 变为 [1,2,3]
a.fill(0) # a 变为 [0,0,0]
a.fill(nil,1,3) # a 变为 [0,nil,nil,nil]
a.fill('a',2..4) # a 变为 [0,nil,'a','a','a']
a[3].upcase! # a 变为 [0,nil,'A','A','A']
a.fill(2..4) { 'b' } # a 变为 [0,nil,'b','b','b']
a[3].upcase! # a 变为 [0,nil,'b','B','b']
a.compact # => [0,'b','B','b']: 移除 nil 元素
a.compact! # 原地移除 nil 元素,a 变为 [0,'b','B','b']
a.clear # a 变为 []
1.3 数组迭代、搜索和排序
数组混入了
Enumerable
模块,因此可以使用所有
Enumerable
的迭代器,同时数组类也定义了一些重要的迭代器和相关的搜索、排序方法:
a = ['a','b','c']
a.each {| elt| print elt } # 基本的 each 迭代器,输出 "abc"
a.reverse_each {|e| print e} # 数组特有的反向迭代器,输出 "cba"
a.cycle {|e| print e } # Ruby 1.9: 无限循环输出 "abcabcabc..."
a.each_index {|i| print i} # 输出索引,输出 "012"
a.each_with_index{|e,i| print e,i} # 同时输出元素和索引,输出 "a0b1c2"
a.map {|x| x.upcase} # 返回元素大写后的数组,返回 ['A','B','C']
a.map! {|x| x.upcase} # 原地修改数组元素为大写
a.collect! {|x| x.downcase!} # collect! 是 map! 的同义词
搜索方法:
a = %w[h e l l o]
a.include?('e') # => true: 检查元素是否存在
a.include?('w') # => false
a.index('l') # => 2: 第一个匹配元素的索引
a.index('L') # => nil: 无匹配元素
a.rindex('l') # => 3: 反向搜索匹配元素的索引
a.index {|c| c =~ /[aeiou]/} # => 1: 第一个元音字母的索引
a.rindex {|c| c =~ /[aeiou]/} # => 4: 最后一个元音字母的索引
排序方法:
a.sort # => %w[e h l l o]: 复制数组并排序
a.sort! # 原地排序,a 变为 ['e','h','l','l','o']
a = [1,2,3,4,5]
a.sort! {|a,b| a%2 <=> b%2} # 按元素对 2 取模排序
洗牌方法(仅 Ruby 1.9):
a = [1,2,3]
puts a.shuffle # 随机打乱数组,例如 [3,1,2]
1.4 数组比较
两个数组相等的条件是元素数量相同、元素值相同且顺序相同。
==
方法和
eql?
方法用于比较元素的相等性,大多数情况下结果相同。数组类实现了
<=>
运算符,用于定义数组的顺序:
[1,2] <=> [4,5] # => -1: 因为 1 < 4
[1,2] <=> [0,0,0] # => +1: 因为 1 > 0
[1,2] <=> [1,2,3] # => -1: 第一个数组较短
[1,2] <=> [1,2] # => 0: 两个数组相等
[1,2] <=> [] # => +1: 空数组小于非空数组
1.5 数组作为栈和队列
可以使用
push
和
pop
方法将数组作为栈使用,实现后进先出(LIFO)的功能:
a = []
a.push(1) # a 变为 [1]
a.push(2,3) # a 变为 [1,2,3]
a.pop # => 3: a 变为 [1,2]
a.pop # => 2: a 变为 [1]
a.pop # => 1: a 变为 []
a.pop # => nil: a 仍为 []
使用
push
和
shift
方法将数组作为队列使用,实现先进先出(FIFO)的功能:
a = []
a.push(1) # a 变为 [1]
a.push(2) # a 变为 [1,2]
a.shift # => 1: a 变为 [2]
a.push(3) # a 变为 [2,3]
a.shift # => 2: a 变为 [3]
a.shift # => 3: a 变为 []
a.shift # => nil: a 仍为 []
1.6 数组作为集合
数组类实现了
&
、
|
和
-
运算符,用于执行集合的交集、并集和差集操作。还可以使用
include?
方法检查元素是否存在,使用
uniq
和
uniq!
方法移除重复元素:
[1,3,5] & [1,2,3] # => [1,3]: 集合交集
[1,1,3,5] & [1,2,3] # => [1,3]: 移除重复元素
[1,3,5] | [2,4,6] # => [1,3,5,2,4,6]: 集合并集
[1,3,5,5] | [2,4,6,6] # => [1,3,5,2,4,6]: 移除重复元素
[1,2,3] - [2,3] # => [1]: 集合差集
[1,1,2,2,3,3] - [2, 3] # => [1,1]: 并非所有重复元素都被移除
small = 0..10.to_a # 小数字集合
even = 0..50.map {|x| x*2} # 偶数集合
smalleven = small & even # 集合交集
smalleven.include?(8) # => true: 检查元素是否存在
[1, 1, nil, nil].uniq # => [1, nil]: 移除重复元素
在 Ruby 1.9 中,数组类定义了计算排列、组合和笛卡尔积的方法:
a = [1,2,3]
# 迭代所有可能的 2 元素子数组(顺序有关)
a.permutation(2) {|x| print x } # 输出 "[1,2][1,3][2,1][2,3][3,1][3,2]"
# 迭代所有可能的 2 元素子集(顺序无关)
a.combination(2) {|x| print x } # 输出 "[1, 2][1, 3][2, 3]"
# 返回两个集合的笛卡尔积
a.product(['a','b']) # => [[1,"a"],[1,"b"],[2,"a"],[2,"b"],[3,"a"],[3,"b"]]
[1,2].product([3,4],[5,6]) # => [[1,3,5],[1,3,6],[1,4,5],[1,4,6], ... ]
1.7 关联数组方法
可以使用
assoc
和
rassoc
方法将数组作为关联数组或哈希使用:
h = { :a => 1, :b => 2}
a = h.to_a # => [[:b,2], [:a,1]]: 转换为关联数组
a.assoc(:a) # => [:a,1]: 查找键为 :a 的子数组
a.assoc(:b).last # => 2: 键为 :b 的值
a.rassoc(1) # => [:a,1]: 查找值为 1 的子数组
a.rassoc(2).first # => :b: 值为 2 的键
a.assoc(:c) # => nil: 无匹配键
a.transpose # => [[:a, :b], [1, 2]]: 交换行和列
1.8 其他数组方法
# 转换为字符串
[1,2,3].join # => "123": 将元素转换为字符串并连接
[1,2,3].join(", ") # => "1, 2, 3": 可选分隔符
[1,2,3].to_s # => "[1, 2, 3]" in Ruby 1.9; "123" in Ruby 1.8
[1,2,3].inspect # => "[1, 2, 3]": 更适合调试
# 二进制转换
[1,2,3,4].pack("CCCC") # => "\001\002\003\004"
[1,2].pack('s2') # => "\001\000\002\000"
[1234].pack("i") # => "\322\004\000\000"
# 其他方法
[0,1]*3 # => [0,1,0,1,0,1]: * 运算符重复数组
[1, [2, [3]]].flatten # => [1,2,3]: 递归扁平化数组
[1, [2, [3]]].flatten(1) # => [1,2,[3]]: 指定扁平化级别
[1,2,3].reverse # => [3,2,1]: 反转数组
a=[1,2,3].zip([:a,:b,:c]) # => [[1,:a],[2,:b],[3,:c]]: 压缩数组
a.transpose # => [[1,2,3],[:a,:b,:c]]: 交换行和列
2. 哈希操作
哈希是 Ruby 中另一种重要的数据结构,用于存储键值对。
2.1 创建哈希
可以使用字面量、
Hash.new
方法或
Hash
类的
[]
运算符创建哈希:
{ :one => 1, :two => 2 } # 基本哈希字面量语法
{ :one, 1, :two, 2 } # Ruby 1.8 弃用语法
{ one: 1, two: 2 } # Ruby 1.9 语法,键为符号
{} # 空哈希对象
Hash.new # => {}: 创建空哈希
Hash[:one, 1, :two, 2] # => {one:1, two:2}
在方法调用中作为最后一个参数时,可以省略哈希字面量的花括号:
puts :a=>1, :b=>2 # 省略花括号
puts a:1, b:2 # Ruby 1.9 语法也适用
2.2 哈希索引和成员测试
哈希可以高效地查找与给定键关联的值,也可以查找与值关联的键,但可能不高效,且多个键可能映射到同一个值:
h = { :one => 1, :two => 2 }
h[:one] # => 1: 查找与键关联的值
h[:three] # => nil: 键不存在
h.assoc :one # => [:one, 1]: 查找键值对
h.index 1 # => :one: 查找与值关联的键
h.index 4 # => nil: 无映射
h.rassoc 2 # => [:two, 2]: 匹配值的键值对
哈希定义了多个同义词方法用于测试成员资格:
h = { :a => 1, :b => 2 }
# 检查键是否存在
h.key?(:a) # true
h.has_key?(:b) # true
h.include?(:c) # false
h.member?(:d) # false
# 检查值是否存在
h.value?(1) # true
h.has_value?(3) # false
fetch
方法是
[]
的替代方法,可处理键不存在的情况:
h = { :a => 1, :b => 2 }
h.fetch(:a) # => 1: 与 [] 功能相同
h.fetch(:c) # 抛出 IndexError
h.fetch(:c, 33) # => 33: 使用指定默认值
h.fetch(:c) {|k| k.to_s } # => "c": 调用块处理
使用
values_at
方法一次提取多个值:
h = { :a => 1, :b => 2, :c => 3 }
h.values_at(:c) # => [3]
h.values_at(:a, :b) # => [1, 2]
h.values_at(:d, :d, :a) # => [nil, nil, 1]
使用
select
方法提取满足条件的键值对:
h = { :a => 1, :b => 2, :c => 3 }
h.select {|k,v| v % 2 == 0 } # Ruby 1.8: => [:b,2]; Ruby 1.9: => {:b=>2}
2.3 存储键值对
可以使用
[]=
运算符或
store
方法将值与键关联:
h = {}
h[:a] = 1 # h 变为 {:a=>1}
h.store(:b,2) # h 变为 {:a=>1, :b=>2}
使用
replace
方法用另一个哈希的键值对替换当前哈希的所有键值对:
h.replace({1=>:a, 2=>:b}) # h 变为参数哈希
使用
merge
、
merge!
和
update
方法合并两个哈希的映射:
k = h.merge(j) # 合并为新哈希
{:a=>1,:b=>2}.merge(:a=>3,:c=>3) # => {:a=>3,:b=>2,:c=>3}
h.merge!(j) # 原地修改 h
h.merge!(j) {|key,h,j| h } # 使用 h 中的值
h.merge(j) {|key,h,j| (h+j)/2 } # 使用平均值
h.update(b:4,c:9) {|key,old,new| old } # h 变为 {a:1, b:2, c:9}
h.update(b:4,c:9) # h 变为 {a:1, b:4, c:9}
2.4 移除哈希条目
不能简单地将键映射为
nil
来移除键,应使用
delete
方法:
h = {:a=>1, :b=>2}
h[:a] = nil # h 变为 {:a=> nil, :b=>2 }
h.include? :a # => true
h.delete :b # => 2: h 变为 {:a=>nil}
h.include? :b # => false
h.delete :b # => nil: 键不存在
h.delete(:b) {|k| raise IndexError, k.to_s } # 抛出 IndexError
使用
delete_if
和
reject!
迭代器删除多个键值对:
h = {:a=>1, :b=>2, :c=>3, :d=>"four"}
h.reject! {|k,v| v.is_a? String } # h 变为 {:a=>1, :b=>2, :c=>3 }
h.delete_if {|k,v| k.to_s < 'b' } # h 变为 {:b=>2, :c=>3 }
h.reject! {|k,v| k.to_s < 'b' } # 无变化
h.delete_if {|k,v| k.to_s < 'b' } # 无变化
h.reject {|k,v| true } # => {}: h 不变
使用
clear
方法移除所有键值对:
h.clear # h 变为 {}
2.5 从哈希提取数组
哈希定义了提取数据到数组的方法:
h = { :a=>1, :b=>2, :c=>3 }
h.length # => 3: 键值对数量
h.size # => 3: size 是 length 的同义词
h.empty? # => false
{}.empty? # => true
h.keys # => [:b, :c, :a]: 键数组
h.values # => [2,3,1]: 值数组
h.to_a # => [[:b,2],[:c,3],[:a,1]]: 键值对数组
h.flatten # => [:b, 2, :c, 3, :a, 1]: 扁平化数组
h.sort # => [[:a,1],[:b,2],[:c,3]]: 排序键值对数组
h.sort {|a,b| a[1]<=>b[1] } # 按值排序
2.6 哈希迭代器
哈希类混入了
Enumerable
模块,提供了多种迭代器:
h = { :a=>1, :b=>2, :c=>3 }
h.each {|pair| print pair } # 迭代键值对,输出 "[:a, 1][:b, 2][:c, 3]"
h.each do |key, value|
print "#{key}:#{value} " # 输出 "a:1 b:2 c:3"
end
h.each_key {|k| print k } # 迭代键,输出 "abc"
h.each_value {|v| print v } # 迭代值,输出 "123"
h.each_pair {|k,v| print k,v } # 迭代键值对,输出 "a1b2c3"
shift
方法可用于迭代哈希的键值对:
h = { :a=> 1, :b=>2 }
print h.shift[1] while not h.empty? # 输出 "12"
2.7 默认值
默认情况下,查询未关联值的键时,哈希返回
nil
,可以指定默认值或提供代码块来计算值:
empty = {}
empty["one"] # nil
empty = Hash.new(-1) # 指定默认值
empty["one"] # => -1
empty.default = -2 # 更改默认值
empty["two"] # => -2
empty.default # => -2: 返回默认值
提供代码块计算默认值:
empty = Hash.new {|hash, key| key.to_s.upcase }
empty["one"] # => "ONE"
综上所述,Ruby 中的数组和哈希提供了丰富的操作方法,能够满足各种数据处理需求。通过合理使用这些方法,可以提高代码的效率和可读性。
3. 数组与哈希操作总结与对比
3.1 操作总结表格
为了更清晰地对比数组和哈希的操作,我们整理了以下表格:
|操作类型|数组操作|哈希操作|
| ---- | ---- | ---- |
|创建|
a = ['a', 'b', 'c']
;
a = Array.new
|
h = { :one => 1, :two => 2 }
;
h = Hash.new
|
|元素提取|
a[0,2]
;
a.values_at(0,2)
|
h[:one]
;
h.values_at(:one)
|
|元素修改|
a[0] = 0
;
a.insert(1, 1, 2)
|
h[:one] = 3
;
h.store(:three, 3)
|
|元素删除|
a.delete_at(0)
;
a.delete(1)
|
h.delete(:one)
;
h.delete_if {|k,v| v % 2 == 0 }
|
|合并操作|
a + a
;
a.concat([4,5])
|
h.merge(j)
;
h.merge!(j)
|
|迭代操作|
a.each {|elt| print elt }
|
h.each {|k,v| print k, v }
|
|搜索操作|
a.include?('a')
;
a.index('a')
|
h.key?(:one)
;
h.index(1)
|
|排序操作|
a.sort
;
a.sort!
|
h.sort
;
h.sort {|a,b| a[1]<=>b[1] }
|
3.2 操作流程对比
以下是数组和哈希在常见操作上的流程对比 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{操作类型}:::decision
B -->|创建| C(数组创建):::process
B -->|创建| D(哈希创建):::process
B -->|元素提取| E(数组元素提取):::process
B -->|元素提取| F(哈希元素提取):::process
B -->|元素修改| G(数组元素修改):::process
B -->|元素修改| H(哈希元素修改):::process
B -->|元素删除| I(数组元素删除):::process
B -->|元素删除| J(哈希元素删除):::process
B -->|合并操作| K(数组合并):::process
B -->|合并操作| L(哈希合并):::process
B -->|迭代操作| M(数组迭代):::process
B -->|迭代操作| N(哈希迭代):::process
B -->|搜索操作| O(数组搜索):::process
B -->|搜索操作| P(哈希搜索):::process
B -->|排序操作| Q(数组排序):::process
B -->|排序操作| R(哈希排序):::process
C --> S([结束]):::startend
D --> S
E --> S
F --> S
G --> S
H --> S
I --> S
J --> S
K --> S
L --> S
M --> S
N --> S
O --> S
P --> S
Q --> S
R --> S
4. 实际应用场景分析
4.1 数组的应用场景
- 顺序存储数据 :当需要按顺序存储一组数据时,数组是一个很好的选择。例如,存储学生的成绩列表:
scores = [85, 90, 78, 92, 88]
- 循环处理数据 :数组的迭代器可以方便地对数据进行循环处理。例如,计算学生的平均成绩:
sum = 0
scores.each {|score| sum += score }
average = sum / scores.length
-
模拟栈和队列
:可以使用数组的
push、pop、shift等方法模拟栈和队列,用于解决一些算法问题。
4.2 哈希的应用场景
- 存储键值对数据 :当需要存储具有关联关系的数据时,哈希非常合适。例如,存储学生的姓名和对应的成绩:
student_scores = { "Alice" => 85, "Bob" => 90, "Charlie" => 78 }
- 快速查找数据 :哈希可以通过键快速查找对应的值,效率很高。例如,查找某个学生的成绩:
score = student_scores["Alice"]
- 配置信息存储 :在程序中,哈希可以用于存储配置信息,方便读取和修改。例如:
config = { "host" => "localhost", "port" => 8080, "username" => "admin" }
5. 性能考虑
5.1 数组性能
- 访问元素 :数组通过索引访问元素的时间复杂度为 O(1),非常高效。
- 插入和删除元素 :在数组的末尾插入或删除元素的时间复杂度为 O(1),但在中间插入或删除元素时,需要移动后续元素,时间复杂度为 O(n)。
-
搜索元素
:使用
include?、index等方法搜索元素时,需要遍历数组,时间复杂度为 O(n)。
5.2 哈希性能
- 访问元素 :哈希通过键访问值的时间复杂度平均为 O(1),但在哈希冲突较多的情况下,性能可能会下降。
- 插入和删除元素 :插入和删除元素的时间复杂度平均为 O(1)。
- 搜索元素 :通过键搜索值的时间复杂度平均为 O(1),但搜索与值关联的键时,需要遍历哈希,时间复杂度为 O(n)。
在实际开发中,需要根据具体的需求和数据量来选择合适的数据结构,以达到最佳的性能。
6. 总结与建议
6.1 总结
Ruby 中的数组和哈希是非常强大的数据结构,它们各自具有独特的特点和用途。数组适用于顺序存储和处理数据,支持各种索引和迭代操作;哈希适用于存储键值对,能够高效地通过键查找值。
6.2 建议
- 选择合适的数据结构 :根据数据的特点和操作需求,选择数组或哈希。如果需要按顺序处理数据,优先考虑数组;如果需要通过键快速查找值,优先考虑哈希。
- 合理使用方法 :熟悉数组和哈希的各种方法,根据具体情况选择合适的方法,以提高代码的效率和可读性。
- 注意性能问题 :在处理大量数据时,要注意数组和哈希的性能特点,避免出现性能瓶颈。
通过深入理解和掌握 Ruby 中数组和哈希的操作,能够更好地处理各种数据,编写出高效、简洁的代码。
超级会员免费看
8

被折叠的 条评论
为什么被折叠?



