Ruby TRICK 2015 获奖作品解读从坏例子中理解 Ruby 语言特性与代码高尔夫技巧【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby本文围绕 Ruby 仓库 sample/trick2015 目录中的 TRICK 20152nd Transcendental Ruby Imbroglio ContestrubyKaigi 举办的第二届 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.rbkinabaBest piphilology最佳圆周率诗歌金奖 Goldksk_1/entry.rbkskMost unreadable ALU最不可读的逻辑运算银奖 Silvermonae/entry.rbmonaeDoubling amphisbaena award双头蝘蜓奖铜奖 Bronzeeregon/entry.rberegonLeast general solver最不通用的求解器第 4 名ksk_2/entry.rbkskMost general solver最通用的求解器第 5 名这些文件均遵循 MIT 许可。每个子目录下除entry.rb外还包含remarks.markdown作者的运行说明、原理解析与局限性分析和authors.markdown其中 ksk_2 目录额外附带了sample.cnf、unsat.cnf、quinn.cnf、abnormal.cnf、uf20-01.cnf五个测试数据文件。二、金奖kinaba 的圆周率诗歌Piphilology运行方式按 kinaba/remarks.markdown 的说明直接无参数运行即可$ ruby entry.rb作者确认该作品可在 ruby 2.2.3p173x64-mingw32上运行。核心思想token 长度即数字Piphilology是借助诗歌记住 π 各位数字的传统英文诗歌中每个单词的字母数依次为 3、1、4、1、5……10 个字母对应数字0。kinaba 的作品把这一技巧移植到 Rubyentry.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 中披露了几个关键技术点10000 位是实打实算出来的使用的级数公式为Pi/2 1 1/3 1/3*2/5 1/3*2/5*3/7 ...即莱布尼茨型乘积级数对应源码开头的big, temp Array 100000000**0x04e2以大整数数组模拟高精度小数。token 不是空格分隔的单位。例如a*b cdef表示的不是 [3,1,4] 而是 [1,1,1,1,4]。这个token 长度负担对可写的代码构成强约束。在 π 中找代码并不现实。虽然 π 被认为包含一切数字序列但受 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 可见实际作品就是把这段内核的每个语句拆散用Numeric、Enumerable、Dir、Fiber等大量无意义常量 token 和开头的实例变量噪声补齐到精确的 token 长度从而让全文 token 长度序列恰好是 π。三、银奖ksk_1 的无分支、无算术Collatz 序列运行方式与背景按 ksk_1/remarks.markdownruby entry.rb 27程序输出从给定正整数开始的 CollatzHOTPO偶数减半、奇数乘 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(3x1?))) endHOTPO 步骤完全由/(.)...\1/ ~ eval(...)的匹配索引完成当n为偶数时eval内部由n个,片段拼接双引号开/闭角色交替最终拼出形如,,,,,,,...逗号数为5n/2的字符串正则(.)...\1一个字符 任意 3 字符 回溯引用 恰好命中末尾的,,,,,匹配索引为n/2当n为奇数且大于 1时数组最后一个元素变成, ?].join#eval结果中出现(n-1)/2个3x1?片段正则命中?, ?匹配索引为3n1当n 1时字符串中只有一个?匹配必然失败返回nil循环终止。字符串中的3x1本可以是任意四字符词作者特意选用它因为 Collatz 猜想也被称为 3x1 问题。变体与局限变体Collatz 猜想可等价表述为任何起点最终都会进入 4→2→1 的循环。此时不必特判n 1把正则换成//并去掉填充,,,,,即可代价是程序将永远运行。局限该实现即使对较小的起始数也要操作超长字符串——从 1819 出发序列最大可升至 1,276,936在 Ruby 1.9.3 上会触发 SystemStackErrorRuby 2.0.0 与 2.2.3 上可以工作。四、铜奖monae 的双头蝘蜓Quine运行方式管道自嵌套按 monae/remarks.markdownruby entry.rb ruby entry.rb | ruby ruby entry.rb | ruby | ruby ruby entry.rb | ruby | ruby | ruby ...作者确认在 ruby 2.2.3p173 与 2.0.0p353 上可运行。原理一个打印两份自身的 quineentry.rb867 字节是一个 quinequine 即自我复制程序。其代码主体是 ASCII 艺术;;与xx排成图案x被gsub掉后剩下的;、空格等字符参与构成可执行代码——程序先把自己的图形解码为一层 Ruby 代码$s%q[...]保存、.gsub(/\x.*|\s/,)清理等再执行eval输出。Amphisbaena双头蝘蜓之名来自古罗马老普林尼《自然史》8.85.1 的引述蝘蜓首尾皆头从一个头喷出毒液尚且不够。这里的双关在于程序在略复杂的基底上打印两份自身且输出本身又是可再次执行产生同样输出的 quine——首尾两个头都能咬出完整的自己这正是铜奖名Doubling amphisbaena的由来。五、第 4 名eregon 的 302 字符数独全解器运行方式按 eregon/remarks.markdown无参数直接运行ruby entry.rb作者确认在 Linuxruby 2.3.0dev / 2.2.2 / 2.0.0、Darwinruby 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.yield与resume模拟协程间的格子之舞一端是解的产出另一端是程序结束程序只用无限循环、没有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.markdownruby entry.rb data输入为DIMACS CNF 格式。示例sample.cnfc 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$| $)(? )/xgolf 化后的输出部分由eval(x?1)*i-1完成MatchData形如#MatchData --- 1:- 2:- 3:被翻译为1 2 -3。remarks 还指出该思路源于 Perl 社区把 3SAT 翻译为正则的经典想法Ruby 版本则直接从 DIMACS 翻译并用前瞻断言换取更短代码与更快匹配。附带的测试数据与已知局限目录内提供了五个测试文件sample.cnf上文示例、unsat.cnf不可满足例、quinn.cnf16 变量 18 子句、abnormal.cnf单条子句跨多行、uf20-01.cnf20 变量 91 子句的 SATLIB 基准。局限性值得注意当变量数超过 99 时正则中\nnn形式的回溯引用可能与八进制字符转义冲突如\502报语法错误而\508合法且 Ruby 1.9.3 曾对部分八进制形态错误返回nil可能使可满足输入被误报为 UNSATISFIABLE。作者补充的好消息是对超过 40 个变量的输入该求解器本来就无法在实用时间内给出解因此该缺陷在实践中不致命。七、从坏例子中学到什么这五件作品恰好覆盖了 Ruby 语言特性的不同切面也是理解 Ruby 仓库 词法与运行时机制的活教材词法层token 长度、alias、%q/%s定界符、?字符字面量、无括号方法定义——kinaba 与 monae 的作品直接建立在这些规则之上正则引擎MatchData、捕获组与回溯引用、前瞻断言的零宽匹配——ksk_1 用匹配索引代替算术ksk_2 用单个正则代替整个 DPLL协程/调度Fiber.yield、Fiber.resume与无参 yield 的返回值——eregon 用它把 81 层回溯压进 9 层栈帧字符串与 evalString#*、join、tr、gsub、eval的组合爆炸——五件作品无一例外。最后再次强调 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),仅供参考