第13章 数组类
系统掌握Array创建、索引、集合/栈/队列语义、增删替换、iterator、矩阵初始化与zip并行访问,控制shape、aliasing和mutation。
学习目标
- 能解释Array.new、%w与%i与to_a与split在“第13章 数组类”中的责任边界
- 能围绕“怎样用容量、索引、元素身份和初始化策略证明数组操作正确?”运行正常与故障轨迹并定位首个分岔
- 能用“第13章 数组类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”
来源、版次与运行边界
“第13章 数组类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第13章 数组类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第13章 数组类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样用容量、索引、元素身份和初始化策略证明数组操作正确?”,本页验收“第13章 数组类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“使用 Array.new 的同一默认对象初始化多行矩阵”;若无法定位第一处状态分岔,就拒绝当前解释。
从“Array只是可变列表”开始深化
Array同时可作sequence、stack、small queue、tuple-like record和matrix row,但每种角色有不同shape与operation contract。避免同一对象被多种写法随意修改。
先预测:Array.new(3, [])得到三行矩阵后修改matrix[0] << 1,为何三行都变化?三个槽位保存同一Array reference。正确独立行使用block generator;这是本章反复出现的aliasing主线。
复习数组
Array有序、以0为起点、可变,槽位保存object references。length/size是槽位数量,不保证无nil或元素同class。Assignment复制Array reference,dup只复制外层,nested objects仍共享。
决定是否可用bang method、是否需要snapshot和并发如何同步。Public method接收Array时应说明会否修改或保留reference;防御copy也只有在定义了depth后才有意义。
数组的创建方法:Array.new、%w与%i
Literal适合已知元素;Array.new(size)创建nil slots;带block版本按index生成value;static default重复同一reference。%w[...]产生Strings,%i[...]在支持版本产生Symbols,按whitespace切项,适合无空格的固定tokens。
values = Array.new(4) { |index| index * index }
words = %w[ruby array iterator]
keys = %i[name score status]
p values: values, words: words, keys: keys使用to_a与字符串的split方法
Range/Enumerator等的to_a materialize全部元素,可能无界或占大量memory;先确认size。String#split是parser,不只是conversion:separator、limit、trailing empty fields和encoding影响shape。CSV不要用简单comma split,改用csv library。
p((1..5).to_a)
p("a,b,,c".split(",", -1)) # preserve trailing/empty shape by explicit policy保护memory和schema。
索引的使用方法
array[index]缺失返回nil,negative index从尾部;fetch把缺失变成IndexError或显式default。Range/start, length可取得slice;越界slice的nil/empty差异需按目标版本测试。应由调用场景选择宽松或严格API。
获取、替换与插入元素
items = %w[a b c]
p items[0]
p items.fetch(-1)
p items[1, 2]
items[1] = "B"
items.insert(2, "X")
items[1, 2] = ["m", "n", "o"]
p itemsSlice assignment可替换不同数量元素,从而改变length;超出尾部index赋值会填nil形成sparse Array。Fixed-shape record/matrix不应暴露任意[]=,而应封装validation。
通过多个索引创建数组
values_at(0, 2, -1)按给定indices构造新Array,missing位置可成为nil;可混Range但展开size需评估。它复制element references,不复制元素。对required schema先fetch每个index,避免nil悄悄进入结果。
作为集合的数组
Array提供& intersection、| union、- difference,结果去重/判断依赖元素equality/hash实现,并保留API规定的order;它不是专门Set的所有语义。适合小型有序结果。
left = [:a, :b, :b, :c]
right = [:b, :d]
p(left & right)
p(left | right)
p(left - right)|与+的不同点
+连接Arrays并保留重复,|执行union-style去重。二者都返回new Array,不修改receivers;concat修改receiver。选择method表达“sequence concatenation”还是“set union”,不要只看输出恰好相同的happy case。
作为列的数组
原书把Array作为“列/队列”讨论:stack用push/<<和pop操作尾部;queue可push后shift头部。Array头部shift/unshift可能移动大量elements,规模大或吞吐高时使用适合queue的数据结构/库并benchmark。
数组的添加、删除与替换
添加有push、<<、unshift、insert、concat;删除有pop、shift、delete、delete_at、delete_if、compact!;替换有index/slice assignment、replace、map!。尤其关注bang返回值。
某些bang methods在“没有变化”时返回nil,而非总返回receiver;不能用其truthiness代表处理成功。delete(value)未找到时返回nil或block result;若Array允许nil元素,presence与returned nil需分开。
items = [1, nil, 2, 2]
removed = items.delete(2)
changed = items.compact!
p items: items, removed: removed, changed: changed对外共享Array原地修改会影响所有references;需要transactional update时先构造new Array并验证,再一次替换owner field。Exception-safe mutation要测试中途block raise后的partial state。
数组与迭代器
Each、each_index、each_with_index、reverse_each以及Enumerable的map/select/reject/find/any?/all?/each_slice等提供不同yield。无block时多数返回Enumerator。比一律each更有信息。
处理数组中的元素
Index loop适合需要邻居/random access;each适合逐元素side effect;map适合一对一new Array;select/reject过滤;delete_if原地删除。遍历中不要通用地增删同一Array,专用bang method由实现拥有index更新。
scores = [40, 80, 95]
passed = scores.select { |score| score >= 60 }
labels = scores.map.with_index { |score, index| "#{index}:#{score}" }
p passed: passed, labels: labelsmap.with_index由map返回Enumerator后附加index,避免外部counter。Lazy chain最终必须由take/force/each消费;source mutation与重复消费规则需说明。
矩阵与初始化
Ruby没有内置通用matrix layout语义,nested Arrays常表示rows。独立行必须用nested block,且每行length一致。防止ragged/shared rows。
rows = 2
columns = 3
matrix = Array.new(rows) { Array.new(columns, 0) }
matrix[0][1] = 7
p matrix # [[0, 7, 0], [0, 0, 0]]初始化时的注意事项
Array.new(rows, Array.new(columns, 0))共享row。即使rows独立,若cell default是mutable object还需为每格生成。Large matrix要评估object overhead、cache locality和专业library;nested Array适合教学/小数据,不等于高性能numeric matrix。
同时访问多个数组
left.zip(right)按index形成pairs;较短Array缺失位置填nil,较长的非receiver尾部忽略到receiver长度。防止silent truncation/padding。
names = ["A", "B", "C"]
scores = [90, 80]
rows = names.zip(scores)
p rows # [["A", 90], ["B", 80], ["C", nil]]若length相等是invariant,zip前先比较并raise;若nil是合法score,使用result object/presence flag区分missing。多个parallel Arrays常意味着应改为Array of records,避免插入/删除时错位。
正式节点与章专属证据
- ↡在“第13章 数组类”中,Array.new、%w与%i必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,to_a与split必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,索引的使用方法必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,作为集合的数组必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,作为列的数组必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,数组的添加、删除与替换必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,数组与迭代器必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,矩阵与初始化必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
- ↡在“第13章 数组类”中,同时访问多个数组必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样用容量、索引、元素身份和初始化策略证明数组操作正确?
输入与接收者
固定Array.new、%w与%i所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明to_a与split的允许状态。
观察证据
保存第13章 数组类的初值、参数、编码或资源位置。
正式节点:Array.new、%w与%i、to_a与split、索引的使用方法、作为集合的数组、作为列的数组、数组的添加、删除与替换、数组与迭代器、矩阵与初始化、同时访问多个数组
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定Array.new、%w与%i的输入和接收者
- 02执行to_a与split并记录状态
- 03观察索引的使用方法的返回或副作用
- 04用同时访问多个数组核对不变量并复位
运行不变量:所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。
边界故障探针
一次只破坏一个前提
本章回顾:每个Array先固定role与shape
- Creation决定reference ownership;static mutable default共享,materialization必须有size/parser上界。
- Index API在missing、negative、slice和sparse write上有宽松语义,required shape使用fetch/validation。
- Set-like、stack、queue分别有equality/order/cost差异,
+、|与concat表达不同意图。 - Mutation method的receiver/result/partial failure要测试,iterator按transform/filter/side effect选择。
- Matrix避免共享rows/cells,zip明确length/padding;复杂parallel Arrays改为records。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样用容量、索引、元素身份和初始化策略证明数组操作正确?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“使用 Array.new 的同一默认对象初始化多行矩阵”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联Array.new、%w与%i、to_a与split、索引的使用方法、作为集合的数组、作为列的数组、数组的添加、删除与替换、数组与迭代器、矩阵与初始化、同时访问多个数组,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Array.new、%w与%i
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- to_a与split
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 索引的使用方法
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 作为集合的数组
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 作为列的数组
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 数组的添加、删除与替换
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 数组与迭代器
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 矩阵与初始化
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 同时访问多个数组
“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。