Ruby 数组
数组是 Ruby 里最常用的集合类型——有序、可放任意类型、方法极其丰富。Ruby 数组的迭代器(map、select、reduce)几乎是函数式编程的范本,学会它们能让你的代码量减少一半。本章系统过一遍。
1. 创建数组的几种方式
# 字面量(最常用)
nums = [1, 2, 3, 4, 5]
mixed = [1, "two", :three, nil] # Ruby 数组可以放任意类型
# %w 字符串数组
words = %w[apple banana cherry] # ["apple", "banana", "cherry"]
# Array.new
a = Array.new(3) # [nil, nil, nil]
b = Array.new(3, 0) # [0, 0, 0](填充同一对象,慎用)
c = Array.new(3) { |i| i * 2 } # [0, 2, 4](用块生成,更安全)
# 范围转数组
r = (1..5).to_a # [1, 2, 3, 4, 5]注意 Array.new(3, 0)(第二参数填值)与 Array.new(3)(带块生成)的区别——前者三个位置指向同一个对象(改一个影响其他),后者各自独立。这是 Ruby 新手的经典坑。
2. 索引(正索引与负索引)
Ruby 数组支持负索引——-1 表示最后一个,-2 表示倒数第二个。这让"取末尾元素"变得极简洁:
a = ["a", "b", "c", "d", "e"]
# 正索引(从 0 开始)
a[0] # "a"
a[2] # "c"
# 负索引(从 -1 开始,从尾部数)
a[-1] # "e"
a[-2] # "d"
# 切片
a[1, 3] # ["b", "c", "d"](从 1 开始取 3 个)
a[1..3] # ["b", "c", "d"](范围,含两端)
a[1...3] # ["b", "c"](范围,不含尾)
# 越界返回 nil(不报错)
a[10] # nil
# first / last
a.first # "a"
a.last(2) # ["d", "e"]切片语法 a[1, 3] 与 a[1..3] 含义不同:前者是"从 1 开始取 3 个",后者是"取索引 1 到 3(含两端)"。注意区分 ..(含尾)和 ...(不含尾)。
3. 增删改查
a = [1, 2, 3]
# 末尾增删
a.push(4) # [1, 2, 3, 4]
a << 5 # [1, 2, 3, 4, 5](<< 是 push 的别名,最常用)
a.pop # 5(弹出末尾,a 变 [1,2,3,4])
# 头部增删
a.unshift(0) # [0, 1, 2, 3, 4]
a.shift # 0(弹出头部)
# 任意位置增删
a.insert(1, "x") # [1, "x", 2, 3, 4]
a.delete_at(1) # "x"(删除索引 1)
a.delete(3) # 删除所有等于 3 的元素
# 合并与去重
[1, 2] + [3, 4] # [1, 2, 3, 4](拼接)
[1, 2, 2, 3].uniq # [1, 2, 3](去重)
[1, 2, 3] - [2] # [1, 3](差集)
[1, 2] & [2, 3] # [2](交集)
[1, 2] | [2, 3] # [1, 2, 3](并集去重)<< 是 push 的别名,实战里最常用,因为它读起来像"往里塞"。集合操作符 + - & | 让数组当成集合用,做去重、交集很方便。
4. 三大迭代器:map / select / reduce
这是 Ruby 数组的灵魂,也是函数式编程的核心。记住它们的返回值类型:
nums = [1, 2, 3, 4, 5]
# each:遍历,返回原数组
nums.each { |x| puts x }
# map:变换,返回新数组(每元素经块处理后)
nums.map { |x| x * 2 } # [2, 4, 6, 8, 10]
nums.map(&:to_s) # ["1", "2", "3", "4", "5"]
# select / reject:过滤
nums.select { |x| x.even? } # [2, 4](保留满足条件的)
nums.reject { |x| x.even? } # [1, 3, 5](剔除满足条件的)
# reduce / inject:聚合
nums.reduce(0) { |sum, x| sum + x } # 15
nums.reduce(:+) # 15(简写)
[1, 2, 3].reduce(:*) # 6(乘积)
# each_with_index:带索引
["a", "b", "c"].each_with_index { |x, i| puts "#{i}: #{x}" }
# 0: a
# 1: b
# 2: c- each:遍历,返回原数组(用于副作用,如 puts)。
- map:变换,返回新数组(1:1 映射)。
- select:过滤,返回新数组(只留满足条件的)。
- reduce:聚合,返回单个值(把数组"折叠"成一个)。
这四个方法加上 &:method 简写,几乎能覆盖 90% 的集合操作需求。
5. 查询与判断
nums = [1, 2, 3, 4, 5]
# 计数与查找
nums.length # 5
nums.count # 5(别名)
nums.count { |x| x.even? } # 2(满足条件的数量)
nums.find { |x| x > 3 } # 4(第一个满足的)
nums.find_all { |x| x > 3 } # [4, 5](所有满足的,等价 select)
# 判断
nums.empty? # false
nums.include?(3) # true
nums.any? { |x| x > 4 } # true(任一满足)
nums.all? { |x| x > 0 } # true(全部满足)
nums.none? { |x| x < 0 } # true(全不满足)
# 最大最小
nums.max # 5
nums.min # 1
nums.minmax # [1, 5]any? / all? / none? 这一组问号方法在条件判断里极高频——比如验证数据是否有效、列表是否非空。
6. 排序、扁平化、分组
# 排序(默认升序)
[3, 1, 4, 1, 5].sort # [1, 1, 3, 4, 5]
[3, 1, 4].sort.reverse # [4, 3, 1](降序)
# 用块自定义排序
["banana", "apple", "cherry"].sort_by { |s| s.length }
# ["apple", "banana", "cherry"](按长度排)
# sort_by 比带块的 sort 更高效(只算一次 key)
# 扁平化
[1, [2, 3], [4, [5]]].flatten # [1, 2, 3, 4, 5]
[1, [2, 3]].flatten(1) # [1, 2, 3](只展一层)
# 反转与旋转
[1, 2, 3].reverse # [3, 2, 1]
[1, 2, 3, 4].rotate(1) # [2, 3, 4, 1](左移)
[1, 2, 3, 4].rotate(-1) # [4, 1, 2, 3](右移)
# 分组与切片
[1, 1, 2, 2, 3].chunk { |x| x }.map { |k, vs| vs }
# [[1, 1], [2, 2], [3]](按连续相同值分组)
(1..10).each_slice(3).to_a
# [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]](每 3 个一组)sort_by 比带块的 sort 高效,因为它对每个元素只算一次排序 key。处理大数组时记得用它。
7. zip 与其他实用方法
# zip:把多个数组按位置配对
[1, 2, 3].zip(["a", "b", "c"])
# [[1, "a"], [2, "b"], [3, "c"]]
names = ["Alice", "Bob"]
ages = [30, 25]
names.zip(ages).to_h # {"Alice"=>30, "Bob"=>25}(转 hash)
# flatten 与 compact
[1, [2, [3]]].flatten # [1, 2, 3]
[1, nil, 2, nil].compact # [1, 2](去 nil)
# sample:随机抽样
(1..10).to_a.sample # 随机一个
(1..10).to_a.sample(3) # 随机 3 个
# group_by:按 key 分组成 hash
(1..6).group_by { |x| x.even? }
# {false=>[1, 3, 5], true=>[2, 4, 6]}
# partition:分成两组
(1..6).partition { |x| x.even? }
# [[2, 4, 6], [1, 3, 5]]group_by 和 partition 是把列表"重组成 hash/二元组"的利器,做统计、分类时很常用。sample 用于做随机抽样,写游戏或测试数据时方便。
小结
本章覆盖了数组的核心方法。重点掌握 map / select / reduce 三剑客——它们是 Ruby 函数式编程的根基。下一篇看哈希,它和数组配合,几乎能表达任何结构化数据。
← 上一篇 Ruby 字符串
下一篇 Ruby 哈希 →