🎄 19/25. 在 Raku 中使用 map 和 Seq 计算 π 的值

欢迎来到Raku One-Liner Advent Calendar的19天!今天,我们将使用两种不同的方法计算π的值。这篇博文的目的是使用不同的方法来生成数字序列。

Pre-party

当然,在 Raku 中你不需要自己计算 π 的值,因为 Raku 给出了一些πpi 形式的预定义常数,以及双倍的值τtau

但为了演示 map 和序列的用法,让我们实现一种最简单的算法来计算 π

img

这是检查答案的草案代码:

1
2
3
4
5
6
7
8
9
my $pi = 1;
my $sign = 1;

for 1..10000 -> $k {
$sign *= -1;
$pi += $sign / ($k * 2 + 1);
}

say 4 * $pi;

第1部分

现在,让我们使用map来使答案紧凑一点。最好使公式更通用:

img

这是我们的第一个单行程序:

1
say 4 * [+] (^1000).map({(-1) ** $_ / (2 * $_ + 1)})

我希望你能理解这段代码里的所有东西。我们在今年的 Advent Calendar 的前几天介绍了该答案的不同部分,例如,在第11天的帖子中

但是,我仍然想强调你需要-1周围的圆括号。如果输入-1 ** $_,则总是得到 -1,因为减号前缀应用于取幂的结果。所以正确的代码是(-1) ** $_

第2部分

尝试使用序列运算符...根据上述公式生成行也很有趣。此外,我们将使用有理数(见第12天)来创建分数⅓,⅕等。

1
2
3
say 4 * [+] <1/1>, 
{-Rat.new($^n.numerator, $^n.denominator + 2)} ...
*.abs < 1E-5;

这个序列以有理数<1/1> 开始,这很方便,因为我们可以立即取其分子和分母。序列的生成器块使用此数字来创建新的有理数,其分母在每次迭代时加2。

你可能会问为什么我引用 $^n.numerator,它总是 1。这是必需的,因为要改变符号,我们需要知道当前值的符号,并且符号保存在有理数值的分子部分中。

占位符变量 $^n 自动取生成器代码块的第一个(并且是仅有的)参数。

生成序列,直到当前值的绝对值变得足够小。用 * ≅ 0 替换最终条件可能很诱人,但该程序运行时间太长而无法看到结果,因为近似相等运算符的默认容差为 10−15,而行不会快速收敛。

此外,你不能使用<... / ...>语法在生成器中创建Rat 数字:

1
{ <$^n.numerator / $^n.denominator + 2> }

在这种情况下,Raku 会将其视为引号构造,例如<red green blue>,而不是代码块,你将获得字符串列表。

这就是今天的一切。明天见!