第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 items

Slice 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可pushshift头部。Array头部shift/unshift可能移动大量elements,规模大或吞吐高时使用适合queue的数据结构/库并benchmark。

数组的添加、删除与替换

添加有push<<unshiftinsertconcat;删除有popshiftdeletedelete_atdelete_ifcompact!;替换有index/slice assignment、replacemap!。尤其关注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: labels

map.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,避免插入/删除时错位。

正式节点与章专属证据

  • :第 1 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 7 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 8 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。
  • :第 9 个正式节点要能回到“所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。”,并说明故障发生前后的第一处差异。

对象与状态模型

从输入、接收者到可观察证据

怎样用容量、索引、元素身份和初始化策略证明数组操作正确?

输入与接收者

固定Array.new、%w与%i所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明to_a与split的允许状态。

观察证据

保存第13章 数组类的初值、参数、编码或资源位置。

正式节点:Array.new、%w与%i、to_a与split、索引的使用方法、作为集合的数组、作为列的数组、数组的添加、删除与替换、数组与迭代器、矩阵与初始化、同时访问多个数组

控制与消息轨迹

在相同初值下定位首个分岔

  1. 01固定Array.new、%w与%i的输入和接收者
  2. 02执行to_a与split并记录状态
  3. 03观察索引的使用方法的返回或副作用
  4. 04用同时访问多个数组核对不变量并复位

运行不变量:所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。

边界故障探针

一次只破坏一个前提

基线满足:所有索引落在有效区间,嵌套元素的共享或复制行为符合声明。

本章回顾:每个Array先固定role与shape

  1. Creation决定reference ownership;static mutable default共享,materialization必须有size/parser上界。
  2. Index API在missing、negative、slice和sparse write上有宽松语义,required shape使用fetch/validation。
  3. Set-like、stack、queue分别有equality/order/cost差异,+|与concat表达不同意图。
  4. Mutation method的receiver/result/partial failure要测试,iterator按transform/filter/side effect选择。
  5. Matrix避免共享rows/cells,zip明确length/padding;复杂parallel Arrays改为records。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样用容量、索引、元素身份和初始化策略证明数组操作正确?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“使用 Array.new 的同一默认对象初始化多行矩阵”,应从哪里开始定位?
  1. 问题 3:覆盖正式节点。 用一个证据包串联Array.new、%w与%i、to_a与split、索引的使用方法、作为集合的数组、作为列的数组、数组的添加、删除与替换、数组与迭代器、矩阵与初始化、同时访问多个数组,说明为什么结论可由另一位读者独立复核。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

Array.new、%w与%i

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

to_a与split

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

索引的使用方法

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

作为集合的数组

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

作为列的数组

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

数组的添加、删除与替换

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

数组与迭代器

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

矩阵与初始化

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

同时访问多个数组

“第13章 数组类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

本章以Ruby 基础教程第 5 版作者支持资料合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…