Day 10 – Wrapping Rats
沿着烟囱向下是一件危险的事情。
烟囱可能很窄,很高,有时候建造得不够好。
今年,圣诞老人想要做好准备。因此,他正在将烟囱检查与交付礼物结合起来。
烟囱检查涉及确保每层砖都处于正确的高度; 即砂浆层的高度是一致的,并且砖的高度也是一致的。
例如,对于 2¼” 高的砖和厚度为 ⅜” 的砂浆,测量序列应该如下所示:
🎅
─██─
||
layer total
░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
⅜ ‾‾???
░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░
2¼ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░
░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░
⅜ ‾‾5⅝
░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░
2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░
░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░
⅜ ‾‾3
░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░
2¼ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░
░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░
⅜ _____________________________________‾‾⅜
这个计划是让精灵们下降到烟囱的底部,手中拿着卷尺,然后回来,确保每个砖块的顶部恰好位于卷尺上的正确位置。
一个名叫猫王的特殊精灵已经自己写了一个程序来帮助完成计算这个高度序列的任务。
因为懒惰,猫王甚至不想添加上述任何分数,并希望程序完成所有工作。他也不想花费精力去找出每层高度的公式。幸运的是,他正在使用 Raku,它将 unicode 分数转换为有理数(类型为 Rat
),并且还有一个序列运算符(...
),它根据前几项计算出算术序列。
所以,猫王在程序中的第一个片段看起来像这样:
my @heights = 0, ⅜, 3, 5+⅝ ... *;
say @heights[^10].join(', ')
这给了他需要的前10个高度:
0, 0.375, 3, 5.625, 8.25, 10.875, 13.5, 16.125, 18.75, 21.375
虽然这是正确的,但很难使用。卷尺只有几分之一英寸,而不是小数。猫王真正想要的输出是分数。
幸运的是,他知道使用 join
将 Rat
s 转换为字符串,通过调用 Rat
类的 Str
方法完成将 Rat
转换为 Str
。因此,通过修改 Rat.Str
的行为,他认为可以使输出更漂亮。
他决定这样做的方式是包装(wrap
) Str
方法(又名使用装饰器模式),如下所示:
Rat.^find_method('Str').wrap:
sub ($r) {
my $whole = $r.Int || "";
my $frac = $r - $whole;
return "$whole" unless $frac > 0;
return "$whole" ~ <⅛ ¼ ⅜ ½ ⅝ ¾ ⅞>[$frac * 8 - 1];
}
换句话说,当把 Rat
字符串化时,除非有小数部分,否则返回整个部分。然后将小数部分视为八分之一数,并将其用作数组中的索引以查找正确的 unicode 分数。
他将这一点与他的第一个程序结合起来,以获得这样的高度:
0, ⅜, 3, 5⅝, 8¼, 10⅞, 13½, 16⅛, 18¾, 21⅜
“万岁!” 他想。 “正是我需要的。”
圣诞老人看了看这个程序,并说:“猫王,这很聪明,但还不够。虽然大多数砖块的尺寸是 ⅛ 的倍数,但砂浆水平可能并非如此。你也可以让你的程序处理这些情况吗?“
“当然”,猫王苦笑着说。然后他将这一行添加到他的包装函数中:
return "$whole {$frac.numerator}⁄{$frac.denominator}"
unless $frac %% ⅛;
使用“可被整除”操作符(%%
),以确保分数可以平分为八分之一,并且如果不是只显式地打印分子和分母。然后,对于 ⅕” 厚的砂浆,序列为:
my @heights = 0, ⅕,
⅕ + 2+¼ + ⅕,
⅕ + 2+¼ + ⅕
+ 2+¼ + ⅕ ... *;
say @heights[^10].join(', ');
0, 1⁄5, 2 13⁄20, 5 1⁄10, 7 11⁄20, 10, 12 9⁄20, 14 9⁄10, 17 7⁄20, 19 4⁄5
“实际上”,圣诞老人说,“现在在我看来,也许这没有用 - 卷尺只有十六分之一英寸,所以最好四舍五入到十六分之一英寸。”
猫王加了一个 round
调用来结束:
Rat.^find_method('Str').wrap:
sub ($r) {
my $whole = $r.Int || '';
my $frac = $r - $whole;
return "$whole" unless $frac > 0;
my $rounded = ($frac * 16).round/16;
return "$whole" ~ <⅛ ¼ ⅜ ½ ⅝ ¾ ⅞>[$frac * 8 - 1] if $rounded %% ⅛;
return "$whole {$rounded.numerator}⁄{$rounded.denominator}";
}
这给了他:
0, 3⁄16, 2⅝, 5⅛, 7 9⁄16, 10, 12 7⁄16, 14⅞, 17¼, 19 13⁄16
他向 Elvira 精灵展示了他的程序,他说:“真是巧合,我写了一个几乎完全一样的程序!除此之外,我也想知道砖层的底部在哪里。我无法使用序列运算符来完成此操作,因为它不是算术级数,但是我可以使用 lazy gather 和匿名有状态变量!就像这样:
my \brick = 2 + ¼;
my \mortar = ⅜;
my @heights = lazy gather {
take 0;
loop { take $ += $_ for mortar, brick }
}
Elvira 的程序产生了:
0, ⅜, 2⅝, 3, 5¼, 5⅝, 7⅞, 8¼, 10½, 10⅞
即砖层的顶部和底部:
\ 🎅 /
██
||
layer total
░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░
⅜ ‾‾8¼
░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░‾‾7⅞
2¼ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░
░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░
⅜ ‾‾5⅝
░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░‾‾5¼
2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░
░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░
⅜ ‾‾3
░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░‾‾2⅝
2¼ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░
░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░
⅜ _____________________________________‾‾⅜
‾‾0
有了他们的程序在手,精灵检查了烟囱,圣诞老人在另一个节日期间没有受伤。