A word on polymod in Raku

在转到 Real 角色的第二部分之前,让我们停下来研究一下 Int 类的中的 polymod 方法。

该方法接收一个数字和任意数字(单位)的列表并返回相应的乘数。所以,你可以很容易地说,例如 550 秒,是 9分 10 秒:

> 550.polymod(60)
(10 9)

在方法调用中,60 的值是一分钟内的秒数。结果中,9 是分钟数,10 是余数,其为秒数。所以,550 秒= 10 秒 + 9 分钟。

如果你想了解更多细节,请添加更多单位。例如,什么是 32768 秒?

> 32768.polymod(60, 60, 24)
(8 6 9 0)

这是 8 秒,6 分钟,9 小时和 0 天。

类似地,132768 秒是 1 天,12 小时,52 分钟和 48 秒:

> 132768.polymod(60, 60, 24)
(48 52 12 1)

老实说,我很难理解它是如何工作的,以及如何读取结果。

文档中的另一个例子更加难以理解:

> 120.polymod(1, 10, 100)
(0 0 12 0)

12 是什么意思?这显然是12倍10.好吧,但我要求给我一些关于数百的信息。我的期望是这样的:120 是 10 的二倍加上 100 的一倍。

试试 121:

> 121.polymod(1, 10)
(0 1 12)

呃,为什么是零?零加上 1x1 加上 12x10? BRR。啊!您不需要在参数中显示地指定 1:

> 121.polymod(10)
(1 12)

这更有意义。除了我还不知道 121 中有几个 100 的事实:

> 121.polymod(10, 100)
(1 12 0)
> 121.polymod(100, 10)
(21 1 0)

现在是时候看看源代码( src/core/Int.pm)了:

method polymod(Int:D: +@mods) {
    fail X::OutOfRange.new(
        :what('invocant to polymod'), :got(self), :range<0..^Inf>
    ) if self < 0;

    gather {
         my $more = self;
         if @mods.is-lazy {
             for @mods -> $mod {
                $more
                    ?? $mod
                    ?? take $more mod $mod
                    !! Failure.new(X::Numeric::DivideByZero.new:
                            using => 'polymod', numerator => $more)
                    !! last;
                $more = $more div $mod;
            }
            take $more if $more;
        }
        else {
            for @mods -> $mod {
                $mod
                    ?? take $more mod $mod
                    !! Failure.new(X::Numeric::DivideByZero.new:
                        using => 'polymod', numerator => $more);
                $more = $more div $mod;
            }
            take $more;
        }
    }
}

该方法有两个分支,一个用于惰性列表,另一个用于非惰性列表。现在让我们只关注第二个分支:

for @mods -> $mod {
    $mod
        ?? take $more mod $mod
        !! Failure.new(X::Numeric::DivideByZero.new:
                       using => 'polymod', numerator => $more);
    $more = $more div $mod;
}

take $more;

好吧,最后一个 take 接收余数,这很容易。在循环中,您将数字除以下一个单位,然后对中间余数进行 “计数”。

我会说我会以不同的方式实现它并切换操作符:

for @mods -> $mod {
    $mod
-           ?? take $more mod $mod
+           ?? take $more div $div
        !! Failure.new(X::Numeric::DivideByZero.new:
                       using => 'polymod', numerator => $more);
-      $more = $more div $mod;
+      $more = $more mod $mod;
}

take $more;

通过这段代码,我可以得到 121 中有几个一百,几个十和几个一:

> 121.polymod(100, 10, 1)
(1 2 1 0)

好的,让我们避免两个 1:

> 1234.polymod(1000, 100, 10, 1)
(1 2 3 4 0)

前面的例子中秒数也可以正常工作:

> 132768.polymod(86400, 3600, 60)
(1 12 52 48)

这是 1 天,12 小时,52 分钟和 48 秒。

正如你所看到的,现在你必须使用明确的单位(8600 而不是 24),你必须按照降序对它们进行排序,但是现在我可以理解和解释结果,而这在原始方法中几乎是不可能的。

Raku 

comments powered by Disqus