38、Ruby 数组与哈希的全面解析

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 中数组和哈希的操作,能够更好地处理各种数据,编写出高效、简洁的代码。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值