资讯详情

资讯详情

mruby-array-ext 深入指南:为 mruby 的 Array 补齐标准库级数组能力

编程语言语言运行时编译器解释器嵌入式【免费下载链接】mrubyLightweight Ruby项目地址https://gitcode.com/gh_mirrors/mr/mruby点击查看免费下载mruby-array-ext是 mruby 官方维护的核心扩展 gem它以Ruby 层 C 层双轨实现的方式为 mruby 内置的Array类补齐了 CRuby 标准库中常用的数组方法集合运算uniq/-/|/、元素访问与修改fetch/fill/rotate/insert、二分搜索bsearch/bsearch_index、组合与排列迭代、过滤与转换等。本文以该 gem 的 README 为主线结合源码实现与测试用例讲解每个方法的用法、边界行为以及隐藏在src/array.c中的哈希集合与线性遍历双路径实现原理帮助你准确地在嵌入式或资源受限环境中使用这套数组扩展。一、gem 定位它给 mruby 的 Array 补了什么mruby 的内核刻意保持精简Array只提供 ISO 语言规范要求的基础方法如[]、push、each、index、delete_at等可参见 src/array.c 的 ROM 方法表。日常 Ruby 编程中大量惯用的数组 API则需要通过mruby-array-ext这样的扩展 gem 来提供。从 README 的概述可以确认该 gem 的目标是提供标准 Ruby 中常见的数组功能functionalities commonly found in standard Ruby即让 mruby 的Array在方法面上向 CRuby 看齐。同时它在实现上并非简单地把 CRuby 代码移植过来而是针对 mruby 的内存模型GC arena、固定整数宽度mrb_int、可选的MRB_NO_FLOAT等做了专门的优化与防御处理这从 NEWS.md 中记录的多条性能与正确性修复如 #7352 统一集合运算的相等性比较、#7360 用 C 遍历实现include?/member?、#7371 为差集构建哈希集合的阈值策略可见一斑。在 mruby 4.0 的演进中该 gem 还持续吸收了Array#find、Array#rfind等新方法见 doc/mruby4.0.md是一个活跃维护、随版本演进的扩展库。二、如何启用与集成该 gem 的元数据在 mrbgem.rake 中声明MIT 协议、作者为 mruby developers、summary 为 Array class extension。它属于 mruby 的核心 gemcore gem不需要网络拉取随源码树直接可用。启用方式有三种任选其一1. 在根级构建配置中引入推荐。编辑根目录的 build_config.rb或参考 build_config/default.rb 的写法MRuby::Build.new do |conf| # ... 其他配置 ... conf.gem :core mruby-array-ext end2. 通过 gembox 批量引入。mruby-array-ext已包含在 mrbgems/stdlib.gembox 中只要你的构建配置加载了 stdlib gembox即可获得全部扩展方法MRuby::Build.new do |conf| conf.gembox stdlib end3. 指定相对路径引入。对部分交叉编译的嵌入式平台构建配置仓库采用了显式路径写法例如 build_config/luckfox_pico.rb 与 build_config/milkv_duo.rbconf.gem mrbgems/mruby-array-ext/IntelGalileo、gameboyadvance、nintendo_wii、playstationportable等平台配置也均以:core mruby-array-ext方式启用如 build_config/IntelGalileo.rb说明这套扩展在内存紧张的嵌入式目标上也是默认组合的一部分。三、方法全景按功能分类逐一讲解以下方法全部由该 gem 添加README 将其分为六组。每个方法后给出的示例来自源码注释与测试用例test/array.rb均可直接复制到mruby含本 gem 的构建中运行验证。3.1 集合运算Set Operations方法语义uniq返回去掉重复值后的新数组不改动原数组uniq!原地去重若没有重复则返回nil-差新数组 原数组副本去掉另一个数组中出现过的所有项\|并拼接两数组并去重交返回两数组共有的元素结果不含重复difference多参数版差集去掉任意一个参数数组中出现过的所有项union多参数版并集依次拼接多个数组并去重intersection多参数版交集返回同时出现在所有参数数组中的元素intersect?两个数组是否有至少一个共同元素返回true/false其中-、|、是运算符形态要求参数必须是数组测试确认传非数组会抛TypeError见 test/array.rb而difference、union、intersection是接受任意多个数组参数的普通方法a [1, 2, 3, 1, 6, 7] b [1, 4, 6] c [1, 5, 7] p a.difference(b, c) # [2, 3] # 去掉 b、c 中出现过的全部项 p a.union(b, c) # [1, 2, 3, 6, 7, 4, 5] # 去重合并 p a.intersection(b, c) # [1] # 只保留三个数组都有的项 p a.intersect?(b) # trueuniq/uniq!还支持块形式按块返回值去重例如按对象的某个字段去重这在 mrblib/array.rb 中通过哈希实现c [[student, sam], [student, george], [teacher, matz]] p c.uniq { |s| s.first } # [[student, sam], [teacher, matz]]3.2 元素访问与修改Element Manipulation and Accessflatten/flatten(level)递归展平为一维数组可选level参数限制递归深度flatten(0)返回原样副本、flatten(1)只展平一层。遇到循环引用数组时抛出ArgumentError: tried to flatten recursive array见 test/array.rb。flatten!/flatten!(level)原地展平无子数组可展平时返回nil。fetch(index)/fetch(index, default)/fetch(index) { |i| ... }越界时抛IndexError除非提供了默认值或块。支持负索引从尾部计数a [11, 22, 33, 44] a.fetch(1) # 22 a.fetch(-1) # 44 a.fetch(4, cat) # cat a.fetch(100) { |i| out: #{i} } # out: 100 a.fetch(100) # IndexError: index 100 outside of array bounds: -4...4fill多种调用形态用对象或块结果填充数组的选中区间。start为nil视为 0length为nil视为数组长度负的start从尾部计数a [a, b, c, d] a.fill(x) # [x, x, x, x] a.fill(w, -1) # [x, x, x, w] a.fill(z, 2, 2) # [x, x, z, z] a.fill(y, 0..1) # [y, y, z, z] a.fill { |i| i * i } # [0, 1, 4, 9] a.fill(1, 2) { |i| i 1 } # [0, 2, 3, 27]fill也可以扩展数组[1, 2].fill(x, 4, 1)会先在空缺处补nil再填充结果为[1, 2, nil, nil, x]见 test/array.rb。compact/compact!去除nil元素compact!无变化时返回nil。rotate(count1)/rotate!(count1)把count位置的元素移到首位。count为负数时反向旋转超过数组长度时按长度取模a [a, b, c, d] a.rotate # [b, c, d, a] a.rotate(2) # [c, d, a, b] a.rotate(-3) # [b, c, d, a] a.rotate(10) # [c, d, a, b]insert(index, obj, ...)在指定索引前插入若干值负索引表示插入位置相对尾部索引超过长度时用nil扩充数组a [1, 2, 3] a.insert(1, 99) # [1, 99, 2, 3] a.insert(5, 99) # [1, 2, nil, nil, nil, 99]自动补 nil a.insert(-2, 99) # 负索引在倒数第二个元素之前插入slice!(index)/slice!(start, length)/slice!(range)删除并返回指定元素或区间越界返回nila [1, 2, 3, 4, 5] a.slice!(0) # 1此时 a 为 [2, 3, 4, 5] a.slice!(1..2) # [3, 4] a.slice!(-1) # 5at(index)等价于ary[index]越界返回nil负索引从尾部计数。dig(idx, ...)按索引序列逐层提取嵌套值任一层越界/为nil时返回nil不会抛异常h [[[1]], 0] h.dig(0, 0, 0) # 1 h.dig(2, 0) # nilfetch_values(idx, ...)按索引列表取值任一索引越界时抛IndexError除非提供块块接收越界的索引并返回替代值a [cat, dog, cow] a.fetch_values(2, 0) # [cow, cat] a.fetch_values(2, 5) # IndexError a.fetch_values(2, 5) { |i| BIRD } # [cow, BIRD]values_at(selector, ...)按整数索引或 Range 选择器取一组元素越界位置填入nila %w{red green purple white none} a.values_at(0, 2, 4) # [red, purple, none] a.values_at(-1, -2, -2, -7) # [none, white, white, nil] a.values_at(4..6, 0...3) # [none, nil, nil, red, green, purple]3.3 搜索与比较Searching and Comparisonassoc(obj)在数组的数组中查找第一个元素子数组的首元素等于obj的子数组返回该子数组或nils1 [colors, red, blue, green] s2 [letters, a, b, c] a [s1, s2, foo] a.assoc(letters) # [letters, a, b, c] a.assoc(foo) # nilrassoc(obj)同上但比较的是每个子数组的第二个元素a [[1, one], [2, two], [3, three], [ii, two]] a.rassoc(two) # [2, two] a.rassoc(four) # nilbsearch { |x| ... }对有序数组做二分查找O(log n)。支持两种模式find-minimum 模式块返回true/false返回第一个使块为true的元素数组须满足前段 false、后段 true的单调性。find-any 模式块返回数值类似 libc 的bsearch(3)返回任一满足块返回 0区间内的元素。两种模式不能混用块返回非法类型非数值、非true/false/nil时抛TypeError。ary [0, 4, 7, 10, 12] ary.bsearch { |x| x 4 } # 4 ary.bsearch { |x| x 6 } # 7 ary.bsearch { |x| x 100 } # nil ary.bsearch { |x| 1 - (x / 4).truncate } # 4 或 7find-any 模式bsearch_index { |x| ... }与bsearch逻辑完全一致区别在于返回的是索引而不是元素本身见 mrblib/array.rb 的实现与bsearch的复用关系。3.4 迭代与组合Iterators and Combinatoricsreverse_each { |item| ... }从尾部向头部迭代返回self无块时返回 Enumerator。该方法是纯 Ruby 循环实现注释标明for efficiencymrblib/array.rb。permutation(nself.size)依次 yield 长度为n的所有排列不带块时返回 Enumerator。permutation(0)产生[[]]n大于数组长度或为负时产生空集a [1, 2, 3] a.permutation.to_a # [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] a.permutation(2).to_a # [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]] a.permutation(0).to_a # [[]]combination(n)依次 yield 长度为n的所有组合无序、不重复a [1, 2, 3, 4] a.combination(2).to_a # [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] a.combination(3).to_a # [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]repeated_combination(n)允许元素重复选取的组合repeated_permutation(n)允许元素重复选取的排列a [1, 2, 3] a.repeated_combination(2).to_a # [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] [1, 2].repeated_permutation(2).to_a # [[1,1],[1,2],[2,1],[2,2]]product(*arys)计算笛卡尔积。不带块时返回全部组合的新数组带块时逐组 yield 并返回self。任一参数为空数组时结果为空任一参数不是数组时抛TypeError[1, 2, 3].product([4, 5, 6], [7, 8, 9]) # [[1,4,7],[1,4,8],...,[3,6,8],[3,6,9]]共 27 组 [1, 2, 3].product([]) # []注意permutation、combination、repeated_*系列在迭代期间修改原数组会抛RuntimeError: array modified during iteration见 src/array.c 的状态校验这是与 CRuby 一致的保护性行为。3.5 转换与其他工具Conversions and Other Utilitiesto_h把数组解释为[key, value]对的序列构造哈希元素不是二元数组时抛TypeError二元数组长度不为 2 时抛ArgumentError支持块形式由每个元素生成键值对[[:foo, :bar], [1, 2]].to_h # {:foo:bar, 12} [1, 2].to_h { |x| [x, x * 2] } # {12, 24} [1].to_h # TypeError: wrong element type Integer [[1]].to_h # ArgumentError: wrong array length (expected 2, was 1)transpose把数组的数组做行列转置各子数组长度不一致时抛IndexError元素不是数组时抛TypeError[[1, 2], [3, 4], [5, 6]].transpose # [[1, 3, 5], [2, 4, 6]]3.6 过滤Filteringdelete_if { |item| ... }删除块求值为true的所有元素返回self无块时返回 Enumerator。reject! { |item| ... }与delete_if行为相同但没有任何删除发生时返回nil。keep_if { |item| ... }删除块求值为false的元素返回self无块时返回 Enumerator。select! { |item| ... }保留块求值为真值的元素有变化时返回self无变化返回nil。a [1, 2, 3, 4, 5] a.delete_if { |i| i 3 } # [1, 2, 3] a.keep_if { |v| v 1 } # [2, 3] a.reject! { |v| v 2 } # [2]再次调用则返回 nil无变化3.7 别名Aliasesappend→pushprepend→unshiftfilter!→select!别名在 mrblib/array.rb 中通过alias一次性声明无需额外实现。3.8 README 之外源码中确认的额外扩展除 README 列出的方法外从源码与测试可以确认该 gem 还向Array追加了以下方法同样属于本 gem 的职责范围README 未逐一列出find(ifnonenil) { |elem| ... }/rfind(ifnonenil) { |elem| ... }从前往后 / 从后往前查找第一个使块为真的元素找不到时若给了ifnone可调用对象则调用之否则返回nilmrblib/array.rb。这是相对 Enumerable 的数组优化版mruby 4.0 起加入见 doc/mruby4.0.md。max/min无块时C 层线性扫描求最大/最小值对 Integer、Float、String 直接用mrb_cmp()比较而不发消息不可比较的类型组合抛ArgumentError如[1, a].max。带块时退化为Enumerable#max/minmrblib/array.rb。include?/member?用mrb_equal()做 C 层遍历与Array#index、#delete的相等语义一致src/array.c。deconstruct模式匹配pattern matching用直接返回数组自身src/array.c。这些方法的行为均有 test/array.rb 中对应assert用例覆盖例如Array#find、Array#rfindtest/array.rb与Array#max, #mintest/array.rb。四、源码架构Ruby 层与 C 层如何分工该 gem 的实现遵循热点在 C、控制流在 Ruby的双层结构mrblib/array.rb定义大部分方法的入口负责参数解析的 Ruby 侧逻辑如fill的多形态参数归一化、块回调、Enumerator 返回、冻结frozen检查然后调用 C 层暴露的私有内部方法__uniq、__fetch、__fill_parse_arg、__fill_exec、__combination_init、__combination_next、__product_generate、__product_next、__normalize_index、__max、__min等完成高性能路径。src/array.cC 层实现核心数据操作。文件末尾的array_ext_rom_entries表src/array.c将这些 C 函数以 ROM 方法表MRB_MT_INIT_ROM方式挂到mrb-array_class上src/array.cmrb_mruby_array_ext_gem_init/final提供 gem 的初始化/清理入口。一个典型的双层协作例子是fetchRuby 层的 fetch 在有块时用__normalize_index归一化索引后在 Ruby 中取元素无块时直接走 C 层的__fetchsrc/array.c由其负责负索引处理、越界判断、默认值回退与IndexError消息生成错误消息格式为index %i outside of array bounds: %i...%i。NONE哨兵常量定义在 mrblib/enum.rb用于区分未提供默认值与默认值为 nil。五、集合运算的底层原理khash 集合与线性遍历双路径src/array.c中最有技术含量的是集合运算uniq、-、|、、difference、union、intersection、intersect?的双策略实现。核心抽象是一个名为ary_membmembership oracle的结构src/array.c它向上层算法屏蔽两种底层策略khash 集合路径use_set true基于 mruby 自带的 khash 构建ary_set_t以mrb_obj_hash_code()为哈希函数、mrb_eql()为相等判定src/array.c。当元素规模足够大时把成员判定压到近似 O(1)。线性遍历路径use_set false直接逐个mrb_eql()比较不分配任何临时结构适合小数组。两条路径的选择阈值由宏SET_OP_HASH_THRESHOLD统一控制定义为8src/array.c。源文件中的注释解释了阈值来由分别对总是线性遍历与总是建集合的构建做基准对比后uniq、|、的线性路径分别在 6、4、8 个元素后开始输给集合路径集合路径从 8、6、12 个元素起领先取 8 作为统一阈值可在不牺牲小规模性能的前提下覆盖全部操作的收益区间。这一抽象是经过多轮重构沉淀下来的。从 NEWS.md 可以追溯到相关修复#7352此前集合路径与线性路径分别用eql?与比较元素导致同一对元素如1与1.01 1.0为真而1.eql?(1.0)为假在不同数组长度下被当作一个元素还是两个元素。修复后两条路径统一走ary_elem_eql即mrb_eql测试 test/array.rb 专门在阈值两侧验证了这一致性。#7372将uniq!、|、重写为统一基于ary_memb的单一算法。#7373将-与intersect?也改为基于 membership oracle消除了两套代码各自实现时的语义漂移。#7368修复Array#intersection的语义错误——集合路径曾把所有参数合并成一个集合再筛选等价于self (a | b | ...)导致某元素只被其中一个参数持有也会存活修复后按参数逐一收窄narrow。对应测试见 test/array.rb。#7371-的集合构建条件是双方都值得建集合total_len 8且self长度 8避免小接收者白白承担大集合的构建成本与 CRuby 的策略一致。#7360include?/member?改为 C 层直接遍历。此外ary_memb的设计还细致处理了 GC 安全khash 的hash回调可能执行任意 Ruby 代码从而可能清空调用方传入的数组因此在建集合前会先用mrb_ary_make_shared_copy()制作共享副本src/array.c线性遍历路径则完全不分配。ARY_MEMB_RUN宏src/array.c确保集合路径即使抛异常也会销毁临时集合而线性路径省去 setjmp 开销。六、边界行为与防御性细节从源码与测试中还可以提炼出一批值得注意的边界语义冻结数组frozen所有破坏性方法uniq!、insert、fill、reject!、select!、flatten!等在接收者是冻结数组时抛FrozenError即使本次调用实际上不产生任何写入例如[1, 2].freeze.insert(0)、[].freeze.uniq!、[1, 2].freeze.fill(9, 0, 0)见 test/array.rb。这是因为是否会写入在调用时无法预知破坏性调用一律视为对冻结对象的修改请求。intersection无参数时仅拷贝等非破坏操作则不触发该错误。整数溢出防护fill的start length、insert的idx argc在计算新长度时使用mrb_int_add_overflow/mrb_int_mul_overflow检测溢出时抛ArgumentError/RangeError防止mrb_int默认 32 位或 64 位溢出导致数组尺寸错误测试见 test/array.rb 与 test/array.rb。NaN 语义uniq、-、、include?对NaN的处理是对象同一性而非相等性——因为NaN不等于任何值包括它自己两个分别构造的NaN会被视为两个不同元素test/array.rb。mrb_int宽度差异测试大量使用skip unless Object.const_defined?(:Float)来兼容MRB_NO_FLOAT构建源码注释也反复提及MRB_INT32下字面量溢出的差异。即该 gem 的行为在无浮点、窄整数等裁剪配置下会相应调整。迭代期修改检测组合/排列迭代器记录初始长度迭代中数组被修改则抛RuntimeErrorsrc/array.c。GC arena 管理uniq的去重遍历中每个元素都mrb_gc_protect且每轮mrb_gc_arena_restore避免重复元素在固定大小 arena 中泄漏槽位修复历史见 test/array.rb 注释。七、测试验证行为契约如何被固化该 gem 的测试体系由两部分组成test/array.rb1180 行覆盖全部公开方法的正常路径、异常路径与边界路径包括assert_permutation_combination等组合类断言助手按排序后的结果比对避免依赖 yield 顺序。特别地测试把阈值两侧行为一致作为一等公民来验证——例如Array#-、Array#|、Array#、Array#intersect?都同时用超过/低于SET_OP_HASH_THRESHOLD的数组断言test/array.rb确保哈希路径与线性路径永不漂移。test/array.c注册了一个仅供测试的内部方法__unshift_from_c直接调用 C APImrb_ary_unshift()用于验证共享存储的数组在 frozen 状态下调用底层 C 写入也会正确抛FrozenError对应 Ruby 侧Array#unshift无法覆盖到的路径见 test/array.rb。如果你修改或移植该 gem运行rake test即可执行上述断言这是验证行为契约最直接的途径。八、实战综合示例下面是一个综合运用本 gem 各类方法的 mruby 脚本涵盖去重、过滤、转置、二分与组合迭代# 集合运算 scores [97, 42, 75, 97, nil, 42] p scores.uniq # [97, 42, 75, nil] p scores.compact # [97, 42, 75, 97, 42] # 过滤 p scores.delete_if { |s| s.nil? } # [97, 42, 75, 97, 42] # 转置二维表 table [[1, 2, 3], [4, 5, 6]] p table.transpose # [[1, 4], [2, 5], [3, 6]] # 二分查找find-minimum 模式 sorted [0, 4, 7, 10, 12] p sorted.bsearch { |x| x 6 } # 7 p sorted.bsearch_index { |x| x 10 } # 3 # 组合枚举 [1, 2, 3].combination(2).to_a # [[1,2],[1,3],[2,3]] # 键值对转哈希 p [[:name, mruby], [:ver, 4]].to_h # {:namemruby, :ver4} # 嵌套取值 nested [[[1]], 0] p nested.dig(0, 0, 0) # 1结语mruby-array-ext是理解 mruby 扩展 gem 设计范式的一个极佳样本它既有面向用户的完整 Ruby 语义方法面、异常、边界行为与 CRuby 对齐又在实现层为嵌入式环境做了针对性优化——双路径集合运算、阈值选择、GC arena 防护、整数溢出检测、共享存储的冻结保护。无论是日常编写 mruby 业务逻辑还是研究如何在裁剪配置下移植 Ruby 标准库能力本文介绍的这些方法与源码细节都值得作为参考。更完整的逐方法注释可继续阅读 mrblib/array.rb 与 src/array.c。赞分享编程语言语言运行时编译器解释器嵌入式【免费下载链接】mrubyLightweight Ruby项目地址https://gitcode.com/gh_mirrors/mr/mruby点击查看免费下载相关推荐mruby-dir 深度解析为 mruby 补齐 Dir 目录操作类的 mrbgem 完整指南mruby dir 深度解析为 mruby 补齐 Dir 目录操作类的 mrbgem 完整指南 mruby dir 是托管在 h2o 仓库 deps/mrub后端网络mruby-sleep 模块全解析为 mruby 注入 Kernelsleep 与 Kernelusleep 延时能力mruby sleep 模块全解析为 mruby 注入 Kernel sleep 与 Kernel usleep 延时能力 导读 mruby sleep 是可观测性日志分析云原生流处理sudoku-solver网格检测深入剖析自适应阈值轮廓分析如何精准锁定歪斜照片里的数独sudoku solver网格检测深入剖析自适应阈值轮廓分析如何精准锁定歪斜照片里的数独 sudoku solver 是一款能「拍照片直接解题」的开源数独求上一篇macOS鼠标指针主题完整指南Windows与Linux系统光标美化下一篇markdown-it-vue 快速上手指南从安装、渲染到问题排查的避坑清单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →