Ruby TRICK 2015 获奖作品解读:从“坏例子“中理解 Ruby 语言特性与代码高尔夫技巧
2026/9/13 11:12:49 网站建设 项目流程

Ruby TRICK 2015 获奖作品解读:从"坏例子"中理解 Ruby 语言特性与代码高尔夫技巧

【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby

本文围绕 Ruby 仓库 sample/trick2015 目录中的 TRICK 2015(2nd Transcendental Ruby Imbroglio Contest,rubyKaigi 举办的第二届 Ruby 代码混淆/极简编程竞赛)获奖作品展开。该目录收录了 2015 年竞赛的全部五件获奖作品及其作者说明,读完本文你将了解:如何用 Ruby 的词法 token 长度"藏"出圆周率前 10000 位、如何用不含任何分支和算术运算的代码求解 Collatz 序列、如何用双头蝘蜓(quine)自我复制,以及如何利用 Ruby 正则的强匹配能力把 SAT 求解器压缩到 194 字节。需要牢记的是:这些代码是"坏例子",只用于欣赏语言极限,切勿作为日常编码范本。

一、目录结构:五件获奖作品一览

sample/trick2015/README.md 对目录内容给出了权威清单:该目录包含 TRICK 2015 竞赛的获奖条目,并明确警告"THESE ARE BAD EXAMPLES! You must NOT use them as a sample code"。竞赛大纲与其他获奖条目见官方页面(README 中引用的 tric/trick2015),仓库内收录的五件作品及其奖项如下:

目录作者作品主题奖项
kinaba/entry.rbkinaba"Best piphilology"(最佳圆周率诗歌)金奖 Gold
ksk_1/entry.rbksk"Most unreadable ALU"(最不可读的逻辑运算)银奖 Silver
monae/entry.rbmonae"Doubling amphisbaena award"(双头蝘蜓奖)铜奖 Bronze
eregon/entry.rberegon"Least general solver"(最不通用的求解器)第 4 名
ksk_2/entry.rbksk"Most general solver"(最通用的求解器)第 5 名

这些文件均遵循 MIT 许可。每个子目录下除entry.rb外,还包含remarks.markdown(作者的运行说明、原理解析与局限性分析)和authors.markdown,其中 ksk_2 目录额外附带了sample.cnfunsat.cnfquinn.cnfabnormal.cnfuf20-01.cnf五个测试数据文件。

二、金奖:kinaba 的"圆周率诗歌"(Piphilology)

运行方式

按 kinaba/remarks.markdown 的说明,直接无参数运行即可:

$ ruby entry.rb

作者确认该作品可在 ruby 2.2.3p173(x64-mingw32)上运行。

核心思想:token 长度即数字

"Piphilology"是借助诗歌记住 π 各位数字的传统,英文诗歌中每个单词的字母数依次为 3、1、4、1、5……10 个字母对应数字0。kinaba 的作品把这一技巧移植到 Ruby:entry.rb(共 1828 字节)中每个词法 token 的字符长度恰好依次给出 π 的十进制位:

$ ruby -r ripper -e \ 'puts Ripper.tokenize(STDIN).grep(/\S/).map{|t|t.size%10}.join' < entry.rb 31415926535897932384626433832795028841971693993751058209749445923078164062862...

并且程序真正运行时也输出 π 的前 10000 位。

内部实现:77 个 token 的 π 计算内核

remarks 中披露了几个关键技术点:

  1. 10000 位是实打实算出来的,使用的级数公式为Pi/2 = 1 + 1/3 + 1/3*2/5 + 1/3*2/5*3/7 + ...(即莱布尼茨型乘积级数,对应源码开头的big, temp = Array 100000000**0x04e2,以大整数数组模拟高精度小数)。
  2. token 不是空格分隔的单位。例如a*b + cdef表示的不是 [3,1,4] 而是 [1,1,1,1,4]。这个"token 长度负担"对可写的代码构成强约束。
  3. 在 π 中"找"代码并不现实。虽然 π 被认为包含一切数字序列,但受 TRICK 的 4096 字符上限约束,若直接等待 π 中出现g += hij所需的 [1,2,3] 序列,按均匀分布平均要消耗 5000 字符才能到达,因此必须"作弊"。

作者用了两类"作弊"技巧:

  • 利用全局变量alias(如alias $curTerm $initTerm),让同一值可以从不同 token 长度的位置访问;
  • srand返回"上一个种子",即srand x表达式在长度 5 的 token 位置上充当了一个"赋值即读旧值"的存储格,无需等待单字母 token=即可写值。

组合这些技巧后,作者构造了一个精心挑选的77 token 的 π 计算程序(remarks 中完整给出,核心片段摘录如下),可以嵌入 π 的前 242 个 token 中;剩余 165 个 token 只是无操作填充物。值得注意的是爆率比 242/77 的前三位"自然是 3.14"。

big, temp = Array 100000000**0x04e2 srand big alias $curTerm $initTerm big += big init ||= big $counter ||= 02 while 0x00012345 >= $counter numbase = 0x0000 $initTerm ||= Integer srand * 0x00000002 srand $counter += 0x00000001 $sigmaTerm ||= init $curTerm /= srand pi, = Integer $sigmaTerm $counter += 1 srand +big && $counter >> 0b1 num = numbase |= srand $sigmaTerm += $curTerm pi += 3_3_1_3_8 $curTerm *= num end print pi

对照 entry.rb 可见,实际作品就是把这段内核的每个语句拆散,用NumericEnumerableDirFiber等大量无意义常量 token 和@开头的实例变量噪声补齐到精确的 token 长度,从而让全文 token 长度序列恰好是 π。

三、银奖:ksk_1 的"无分支、无算术"Collatz 序列

运行方式与背景

按 ksk_1/remarks.markdown:

ruby entry.rb 27

程序输出从给定正整数开始的 Collatz(HOTPO:偶数减半、奇数乘 3 加 1)序列直至到达 1。作者确认在 ruby 1.9.3 / 2.0.0 / 2.2.3 上可运行。Collatz 猜想仍是未决问题,程序对某些数可能不终止(2^60 以下未发现反例)。

核心技巧:用正则匹配索引模拟条件分支

entry.rb 全文只有 106 字节、一行代码,源码中既无条件分支也无算术运算。其等价的可读形式为:

n = ARGV[0].to_i begin # do nothing end while begin puts n n = (/(.)...\1=/ =~ eval('[",,,,,"'+ '",'*n + ' ?=].join#"].join("3x+1?")')) end

HOTPO 步骤完全由/(.)...\1=/ =~ eval(...)的匹配索引完成:

  • n为偶数时,eval内部由n",片段拼接,双引号开/闭角色交替,最终拼出形如,,,,,,=,=...(逗号数为5+n/2)的字符串,正则(.)...\1=(一个字符 + 任意 3 字符 + 回溯引用 +=)恰好命中末尾的,,,,,=,匹配索引为n/2
  • n为奇数(且大于 1)时,数组最后一个元素变成", ?=].join#"eval结果中出现(n-1)/23x+1?片段,正则命中?, ?=,匹配索引为3n+1
  • n = 1时字符串中只有一个?,匹配必然失败返回nil,循环终止。

字符串中的3x+1本可以是任意四字符词,作者特意选用它,因为 Collatz 猜想也被称为 3x+1 问题。

变体与局限

  • 变体:Collatz 猜想可等价表述为"任何起点最终都会进入 4→2→1 的循环"。此时不必特判n = 1,把正则换成/=/并去掉填充",,,,,"即可,代价是程序将永远运行。
  • 局限:该实现即使对较小的起始数也要操作超长字符串——从 1819 出发序列最大可升至 1,276,936,在 Ruby 1.9.3 上会触发 SystemStackError;Ruby 2.0.0 与 2.2.3 上可以工作。

四、铜奖:monae 的"双头蝘蜓"Quine

运行方式:管道自嵌套

按 monae/remarks.markdown:

ruby entry.rb ruby entry.rb | ruby ruby entry.rb | ruby | ruby ruby entry.rb | ruby | ruby | ruby ...

作者确认在 ruby 2.2.3p173 与 2.0.0p353 上可运行。

原理:一个"打印两份自身"的 quine

entry.rb(867 字节)是一个 quine(quine 即自我复制程序)。其代码主体是 ASCII 艺术:;;xx排成图案,xgsub掉后剩下的;、空格等字符参与构成可执行代码——程序先把自己的"图形"解码为一层 Ruby 代码($s=%q[...]保存、.gsub(/\x.*|\s/,"")清理等),再执行eval输出。

"Amphisbaena"(双头蝘蜓)之名来自古罗马老普林尼《自然史》8.85.1 的引述:蝘蜓首尾皆头,"从一个头喷出毒液尚且不够"。这里的双关在于:程序在"略复杂的基底"上打印两份自身,且输出本身又是可再次执行产生同样输出的 quine——首尾两个"头"都能"咬"出完整的自己,这正是铜奖名"Doubling amphisbaena"的由来。

五、第 4 名:eregon 的 302 字符数独全解器

运行方式

按 eregon/remarks.markdown,无参数直接运行:

ruby entry.rb

作者确认在 Linux(ruby 2.3.0dev / 2.2.2 / 2.0.0)、Darwin(ruby 2.0.0、JRuby 9.0.3.0、Rubinius 2.2.6)上均可运行。

核心特性:Fiber 协程 + 回溯的极简解法

entry.rb 共 601 字节(15 行 42 词 600 字符,作者自嘲"喜欢好数字"),能输出任意数独的所有解;给空盘则打印全部完成盘(不作时间承诺)。remarks 揭示了若干实现要点:

  • 求解器本身只有 302 字符,前提是数独已按"行数组"编码在变量s中;
  • 程序实现回溯,且"状态保存方式非常优雅":整体调用深度从不超过 9 层栈帧,却能回溯81 层——因为回溯不靠调用栈递归,而是靠 Fiber 挂起/恢复(Fiber.yieldresume)模拟协程间的"格子之舞":一端是解的产出,另一端是程序结束;
  • 程序只用无限循环、没有break,且同时交织地构造求解器与题目本身(源码中大量"...[1,9,_,_,_,8,_,_,5]+"...数组拼接就是交织的痕迹);
  • 为省字用到的 Ruby 技巧:定义的方法名属于少数可省略括号与空格的写法(见源码首行class String;def[]*a;$*<<a;b;end;end;);利用Fiber.yield无参调用的返回值;把String#b当作极短的self替代品;
  • 设计取舍:由于不允许从 Fiber 中return,程序只好exit;作者还"抱怨"笛卡尔积运算符太长(a.product(a)本可以是a*a)。

灵感来源包括一份多解的报纸数独和论文《Revisiting Coroutines》。局限:程序不接受任何命令行参数,试图传参会被它"安静地退出"。

六、第 5 名:ksk_2 的 194 字节 SAT 求解器(正则的威力)

运行方式与输入格式

按 ksk_2/remarks.markdown:

ruby entry.rb < data

输入为DIMACS CNF 格式。示例(sample.cnf):

c c This is a sample input file. c p cnf 3 5 1 -2 3 0 -1 2 0 -2 -3 0 1 2 -3 0 1 3 0

其中c开头是注释(仅允许出现在p cnf ...行之前),p cnf 3 5表示 3 个变量、5 个子句,每个子句以0结尾。上述公式可满足,程序输出:

s SATISFIABLE v 1 2 -3

不可满足时输出s UNSATISFIABLE。这一输出规范与 SAT 竞赛一致。

内部实现:DIMACS → 单个 Regexp 的翻译

entry.rb 仅 195 字节,思路是把 CNF 翻译成一条正则表达式:每个变量对应一个捕获组(-?)(匹配"-"为 true、""为 false),每个子句翻译成一个正向前瞻断言。上面示例等价于:

'---=' =~ /(-?)(-?)(-?)-*=(?=\1$|-\2$|\3$|$)(?=-\1$|\2$|$)(?=-\2$|\3$|$)(?=\1$|\2$|-\3$|$)(?=\1$|\3$|$)(?=)/

若公式可满足则返回MatchData,否则返回nil。利用正则的x选项,上述翻译可以写成与 DIMACS 结构平行的紧凑形式:

?-*3+'=-'=~/#{'(-?)'*3}-*=(?= \1$| -\2$| \3$| $)(?= -\1$| \2$| $)(?= -\2$| -\3$| $)(?= \1$| \2$| -\3$| $)(?= \1$| \3$| $)(?= )/x

golf 化后的输出部分由eval(x+?1)*i-=1完成:MatchData形如#<MatchData "---=" 1:"-" 2:"-" 3:"">被翻译为"1 2 -3"。remarks 还指出该思路源于 Perl 社区把 3SAT 翻译为正则的经典想法,Ruby 版本则直接从 DIMACS 翻译并用前瞻断言换取更短代码与更快匹配。

附带的测试数据与已知局限

目录内提供了五个测试文件:sample.cnf(上文示例)、unsat.cnf(不可满足例)、quinn.cnf(16 变量 18 子句)、abnormal.cnf(单条子句跨多行)、uf20-01.cnf(20 变量 91 子句的 SATLIB 基准)。

局限性值得注意:当变量数超过 99 时,正则中\nnn形式的回溯引用可能与八进制字符转义冲突(如\502报语法错误而\508合法),且 Ruby 1.9.3 曾对部分八进制形态错误返回nil,可能使可满足输入被误报为 UNSATISFIABLE。作者补充的"好消息"是:对超过 40 个变量的输入,该求解器本来就无法在实用时间内给出解,因此该缺陷在实践中不致命。

七、从"坏例子"中学到什么

这五件作品恰好覆盖了 Ruby 语言特性的不同切面,也是理解 Ruby 仓库 词法与运行时机制的活教材:

  1. 词法层:token 长度、alias%q/%s定界符、?字符字面量、无括号方法定义——kinaba 与 monae 的作品直接建立在这些规则之上;
  2. 正则引擎MatchData、捕获组与回溯引用、前瞻断言的零宽匹配——ksk_1 用匹配索引代替算术,ksk_2 用单个正则代替整个 DPLL;
  3. 协程/调度Fiber.yieldFiber.resume与无参 yield 的返回值——eregon 用它把 81 层回溯压进 9 层栈帧;
  4. 字符串与 evalString#*jointrgsubeval的组合爆炸——五件作品无一例外。

最后再次强调 sample/trick2015/README.md 的警告:这些是"坏例子",它们展示了 Ruby 表达能力的边界,而非工程实践的标准。想深入了解竞赛规则与其他获奖作品,可以查阅 README 中指向的竞赛官方页面;想复现运行,按上文各remarks.markdown给出的命令即可(注意 ksk_1 需传入正整数参数,ksk_2 需从标准输入读取 DIMACS CNF)。

【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby

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

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

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

立即咨询