☰
用 Ruby 的 Arraypermutation 枚举全部合法的 9-ball 摆球排列
2026/10/7 16:17:15 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

在台球 9-ball 项目中,想要得到"所有合法摆球方式"的完整清单,本质是一个带固定锚点的排列(permutation)枚举问题:1 号球锁定在菱形顶端、9 号球锁定在菱形中心,剩下的 2~8 号七颗球可以任意换位。本文以 math/generate-permutations-of-all-valid-9-ball-racks.md 这份笔记为主线,讲解如何用 Ruby 的Array#permutation优雅地生成全部 5040 种合法摆法,并逐行拆解实现、给出验证与落地扩展方案。

9-ball 摆球:问题定义与规则约束

美式 9-ball(九球)的摆球采用菱形(diamond)布局,总共 9 颗球,排成 1-2-3-2-1 的五行结构。合法的摆球必须同时满足两条硬性约束:

  1. 1 号球必须放在菱形顶端(head),即尖角那一颗;
  2. 9 号球必须放在菱形正中心,即第三行三颗球中间的那一颗。

在满足这两条约束之后,其余 7 颗球(2 号到 8 号)可以任意摆放。

如果我们把 9 个位置映射为一个长度为 9 的数组,位置布局如下:

数组下标菱形位置
0顶端(head,固定 1 号球)
1、2第二行两颗球
3、5第三行左右两颗球
4第三行中间(中心,固定 9 号球)
6、7第四行两颗球
8第五行(底端)

这样一来,问题被精确地规约为:为下标 1、2、3、5、6、7、8 这 7 个位置填入 2~8 号七颗球的所有不同次序。

组合数学建模:为什么答案是 7! = 5040

每个合法摆法都可以看作集合{2, 3, 4, 5, 6, 7, 8}的一个全排列,每一颗球在七个位置中恰好出现一次。这正是组合数学中的排列(permutation)问题:

在初等组合数学中,k-排列(k-permutations),或称为部分排列,是从一个集合中选出 k 个不同元素的有序排列;当 k 等于集合大小时,它们就是通常意义上的全排列。

当集合大小恰好等于要填的位置数(都是 7)时,排列数就是阶乘:

7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040

也就是说,合法的 9-ball 摆球方式一共有5040 种。这个数字不依赖任何实现方式,纯粹由组合数学决定:固定 1、9 两颗球后,剩余 7 颗球的自由度为7!。

用 Ruby 的 Array#permutation 枚举全部摆法

Ruby 的Array类内置了#permutation方法,专门用于枚举排列:

  • 不传参数时,[a, b, c].permutation生成集合内全部元素的全排列(即n!个);当集合有 7 个元素时恰好生成7! = 5040个。
  • 传入参数k时生成 k-排列(部分排列),例如[2,3,4,5,6,7,8].permutation(3)只生成每行 3 个元素的排列。
  • 无块调用时返回一个惰性的Enumerator(可以用to_a或count等 Enumerable 方法消费);带块调用时逐项 yield 并在结束后返回原数组本身。

针对 9-ball 问题,只需对七颗自由球做全排列,再在每个排列的固定位置插入 1 号球和 9 号球即可。完整代码如下(来自原笔记,逐字可用):

[2,3,4,5,6,7,8].permutation.map do |perm| [1, *perm[0..2], 9, *perm[3..7]] end.to_a => [[1, 2, 3, 4, 9, 5, 6, 7, 8], [1, 2, 3, 4, 9, 5, 6, 8, 7], [1, 2, 3, 4, 9, 5, 7, 6, 8], [1, 2, 3, 4, 9, 5, 7, 8, 6], [1, 2, 3, 4, 9, 5, 8, 6, 7], [1, 2, 3, 4, 9, 5, 8, 7, 6], [1, 2, 3, 4, 9, 6, 5, 7, 8], ... [1, 8, 7, 6, 9, 5, 3, 2, 4], [1, 8, 7, 6, 9, 5, 3, 4, 2], [1, 8, 7, 6, 9, 5, 4, 2, 3], [1, 8, 7, 6, 9, 5, 4, 3, 2]]

这段代码只有两行主体逻辑,却完整表达了全部规则约束,是"用数据结构承载规则"的典型示范。

代码逐行拆解

1. 生成七颗自由球的全排列

[2,3,4,5,6,7,8].permutation

#permutation不带参数时生成包含全部元素的全排列。注意它是惰性的:不传入块时返回Enumerator,真正的排列序列只在被消费(如map、to_a、count)时才逐一生成,因此即使有 5040 项也不会一次性占满内存。

2. 把排列拆成"中心之前"与"中心之后"两段

perm[0..2] # 下标 0、1、2,共 3 个元素 perm[3..7] # 下标 3、4、5、6,共 4 个元素

perm是长度 7 的排列数组。perm[0..2]取前三个自由球,负责填到 9 号球中心位置之前的下标 1、2、3;perm[3..7]取剩余四个自由球,填到中心位置之后的下标 5、6、7、8。切片两端合计恰好 7 颗球,一个不重不漏。

3. 用 splat 展开并锚定 1 号与 9 号

[1, *perm[0..2], 9, *perm[3..7]]

Ruby 的 splat 运算符*会把数组展开成多个元素插入到新的数组字面量中。这里在字面量里固定写入1(顶端)和9(中心),再把两段自由球展开到对应位置,最终每行都是一个满足全部约束的长度为 9 的摆法数组。

4. map 后 to_a 物化结果

.map do |perm| ... end.to_a

map消费Enumerator并把每个排列转换成对应的 9 球数组;to_a把整个序列物化为可重复使用的Array。去掉to_a得到的是惰性枚举器,适合后续链式处理(例如配合count、select、sample)。

验证与落地扩展

校验数量与约束

可以用 Enumerable 方法直接验证结果符合数学预期:

racks = [2,3,4,5,6,7,8].permutation.map do |perm| [1, *perm[0..2], 9, *perm[3..7]] end racks.count # => 5040,与 7! 一致 racks.uniq.count # => 5040,无重复 racks.all? { |r| r.first == 1 } # 1 号球总在顶端 racks.all? { |r| r[4] == 9 } # 9 号球总在中心 racks.all? { |r| r.sort == (1..9).to_a } # 每行都是 1..9 各一次

这四个断言分别对应:排列数正确、无重复、两条硬性约束成立、每颗球恰好出现一次,四者共同构成对结果完整性的充分验证。

将全量清单导出到文件

如果要把 5040 种摆法持久化,可以直接把结果逐行写出:

File.write("9-ball-racks.txt", racks.map(&:join).join("\n"))

随机采样一副合法摆球

从全量清单中随机取一局,比手工洗牌更可控:

racks.sample # => 例如 [1, 5, 8, 3, 9, 2, 7, 4, 6]

惰性枚举的其他消费方式

因为#permutation返回Enumerator,在构造阶段并不实际生成 5040 个数组,还可以直接消费而无需物化:

[2,3,4,5,6,7,8].permutation.lazy .map { |perm| [1, *perm[0..2], 9, *perm[3..7]] } .first(5) # 只取前 5 种,避免全量计算

这个思路在自由球数量变大(排列数指数增长)时尤为重要。

小结

9-ball 合法摆球的枚举是一个教科书级的组合数学应用:先用规则约束把问题降维为"7 个元素的排列",再用7! = 5040确定规模,最后用 Ruby 内置的Array#permutation加 splat 数组构造,两行代码便完整产出全部合法摆法。原笔记收录于 math/generate-permutations-of-all-valid-9-ball-racks.md,同目录下的 math/convert-arbitrary-number-to-probability-with-sigmoid.md 是另一篇数学主题笔记;仓库 README.md 按类别索引了全部 TIL 笔记,Ruby 相关的更多数组与 Enumerable 技巧可参考 ruby/summing-collections.md、ruby/get-specific-values-from-hashes-and-arrays.md 等条目。

  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

相关推荐

上一篇:LinkSwift:九大网盘直链下载的终极解决方案
下一篇:Pilot Shell Console架构揭秘:Bun、Express、React与SQLite技术栈完整拆解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询