第二十五天 - 以数之名

这个学期学期我参加了我的第一个校对课程,题为“数学证明研讨会简介”。在学习了其他数学课程(微积分,矩阵代数等)之后,我觉得我没有那么多的数学基础,到目前为止,我所做的只是纯粹的计算数学,到处撒上了一些证明。回想起来,我发现课程非常有趣,并且学习不同的定理及其证明,主要来自数论,给了我一个新的数学视角。

你可能会问,“这与 Raku 有什么关系?”。正如我所提到的,课堂上讨论的大多数证明或家庭作业都与数论有关。如果 Raku 和数论有一个共同点就是它们的可访问性。类似于数论的内容如何具体和熟悉,Raku 对初学者来说非常平易近人。事实上,鼓励初学者写出所谓的“婴儿 Perl”。

似乎他们分享的另一件事是他们的浩瀚。例如,在 Raku 中可以找到许多运算符,而在数论中,可以找到从偶数到可爱数字的过多不同类型的数字。在大多数情况下,这些数字很容易理解,如果有一个数字的定义,那么很容易检查该类别中是否包含给定的整数。例如,素数正式定义如下:

如果整数p> 1的唯一正数除数为1且p为p,则称p为素数,或简称为素数。否则,称整数p为合数。

通过使用这个定义,我们可以非常简单地弄清楚某个数字是否是素数。例如,在前十个正整数中,2,3,5和7是素数。对于小数字来说这是微不足道的,但是用更大的数字手工完成它会很快变得单调乏味。这就是 Raku 的用武之地。Raku 提供了许多构造/函数,即使它们不能简化工作,它们也可以简化它。例如,考虑到素数的定义,我们可以轻松实现在 Raku 中测试素数的算法:

sub isPrime( $number ) {
    return $number > 1 if $number ≤ 3;

    loop (my $i = 2; $i² ≤ $number; $i++) {
        return False if $number %% $i;
    }

    return True;
}

请记住,这不是关于编写高性能代码。如果代码以这种方式拒绝,那么它将是优秀的,但它不是目标。我的目的是展示初学者在 Raku 中表达数学结构的容易程度。值得一提的是,Raku 已经包含了is-prime测试素数的子程序(或方法)。然而,尽管对于素数这是正确的,但对于你可能遇到的另一种类型的数字可能并非如此,例如阶乘,因子或甚至加泰罗尼亚数字。在这种情况下,Raku 会很有帮助。

在了解了不同类型的数字后,我开始寻找一些奇特的数字,看看如何使用 Raku 实现它们。在这个过程中,我发现这个有用的网站列出了一堆数字,它们的定义和一些例子。从所有这些中,我选择了四种类型的数字,这些数字并非愚蠢地难以实现(我仍然在编写 Perl 宝宝!😅),同时足以说明一些 Raku 构造。另一方面,我避免了那些可能过于简单的事情。

让我们开始于…

友善的数字

Amicable 数字是一对数字,也称为友好数字,每个数字的等分部分添加给另一个数字。

sub aliquot-parts( $number ) {
   (^$number).grep: $number %% *; 
}

sub infix:<amic>( $m, $n ) {
    $m == aliquot-parts($n).sum &&
    $n == aliquot-parts($m).sum;
}

say 12 amic 28;   # False, 12 and 28 aren't amicables.
say 220 amic 284; # True, 220 and 284 are though.

数字的等分部分是排除数字本身的因素。为了找到数字的等分部分,我创建了一个子程序aliquot-parts,用于1..^$number创建从1到$numbers(不包括)的数字列表。随后对该列表进行了追踪,以找出列表中均匀分割的那些数字$number。在这个片段中,它是通过使用中缀运算符实现的%%True如果第一个操作数可被第二个操作数整除,则返回该操作符。否则,它返回False。第二个操作数代表前面提到的列表中的任何数字,所以我使用过*,在这种情况下,它被称为*任何星形,*并在表达式上创建一个闭包$number %% *。因此,子程序中的整个表达式相当于(^$number).grep: { $number %% $_ };。最后,子程序返回$number排除$number自身的因子列表。

为了确定两个数字是否友好,我们可以只使用一个子程序。但是,Raku允许创建新的运算符,这些运算符只是具有有趣名称的子程序,我就是这么做的。我创建了中缀运算符(意思是两个操作数之间)amicTrue如果两个数字是友好的,则返回。否则,False。如你所见,创建新运算符的语法很简单:关键字sub,后跟运算符的类型(前缀,中缀,后缀等),引用构造内的运算符名称,预期参数和代码块。

Factorion

因子是一个自然数,等于给定基数中其数字的阶乘的总和。

subset Whole of Int where * ≥ 0;

sub postfix:<!>( Whole $N --> Whole ) {
    [*] 1..$N;
}

sub is-factorion( Whole $number --> Bool ) {
    $number == $number.comb.map({ Int($_)! }).sum 
}

say is-factorion(25);  # False
say is-factorion(145); # True

回想一下,通常用数字N表示的阶乘N!是产品1 x 2 x ... x N。例如,3! = 1 x 2 x 3 = 6。在代码片段中,我创建了postfix运算符!以返回整数操作数的阶乘。因此say 3!;,在代码片段和打印中工作得很好6。计算阶乘是如何直接的:范围1..$N创建一个从1到$N(包括)的数字列表然后我使用[...],这被称为减少元运算符,运算符*减少创建的列表1 x 2 x ... $N,有效地给了我阶乘的$N。Raku中有许多运算符,元运算符[...]可以与其中许多运算符一起使用。

至于因子,我想知道一个数字是否是一个因子,所以我创建了一个采用整数并返回一个布尔值的子程序。Raku逐渐输入,因此它允许显式输入变量,指定子的返回类型等。我决定键入子程序的参数和子程序的返回类型。

关于友好数字的部分,我对子程序的论点非常自由。但是,在这里,我决定遵循阶乘的定义,只允许整数,因此定义和使用该Whole类型。在Raku中,运算符subset使用基类型声明一个新类型。但是,如果我没有使用该where条款,那么我最终只会使用另一个名称,这个Int类型将是多余的。所以我使用该where子句来约束对所需输入的任何赋值的类型。在这种情况下,赋值给类型的变量Whole

使用is-factorionsub,我使用该方法comb分解$number成数字,然后用map它们找到各自的阶乘并总结它们。子返回Trueif $number等于其数字的阶乘的总和。False否则返回。

循环数

循环数是具有N个数字的数字,当乘以时1, 2, 3, ..., N,以不同的顺序产生相同的数字。

sub is-cyclic( Int $n --> Bool ) {
    for 1..$n.chars -> $d {
        return False if $n.comb.Bag != ($n * $d).comb.Bag;
    }
    return True;
}

say is-cyclic(142857); # True
say is-cyclic(95678);  # False

在这里,我创建了is-cyclic一个采用整数并返回布尔值的子程序。我使用for循环遍历数字位数(第1个,第2个等)并使用它们乘以每次迭代中的数字。然后我使用之前看到的comb方法,然后使用该Bag方法。在Raku中,a Bag是不同元素的不可变集合,没有特定顺序,其中每个元素按集合中的副本数加权。这是我需要的那种结构,因为只有数字的数字及其数量很重要,而不是它们的顺序,并且Bag完全实现了这一点。False如果行李不具有相同的数字或具有相同的数字但是加权不同,则子程序返回。除此以外,True 返回,表示数字的循环。

快乐的数字

幸福数字由以下过程定义:从任何正整数开始,将数字替换为其在十进制数字中的数字的平方和,并重复该过程,直到该数字等于1(它将保留的位置),或者它在一个不包括1的循环中无休止地循环。

sub is-happy( $n is copy ) {
    my $seen-numbers = :{};
    while $n > 1 {
        return False if $n ∈ $seen-numbers;
        $seen-numbers{$n} = True;
        $n = $n.comb.map(*²).sum
    }
    return True;
}

say is-happy(7);     # True
say is-happy(2018);  # False

在完成定义中描述的过程之后,一个快乐的数字结束等于1.另一方面,一个非快乐的数字跟随一个到达循环的序列,该序列4, 16, 37, 58, 89, 145, 42, 20, 4,…不包括1.有了这个事实,我创建了散列$seen-numbers到跟踪这些数字。如while循环所示,该过程一次又一次地重复,同时$n大于1或直到看到数字。这里突出的线是包含符号∈的线。在集合论中,如果元素p是集合A的成员(或元素),则它由p∈A表示,这正是在此处测试的内容。如果数字$n是散列的元素,则子返回False。否则,它返回True,表示数字的幸福。

摘要

在这篇文章中,我略微进行了逐步打字,如何定义一个新的运算符,使用subset关键字setbag数据结构进行子类化。正如你可能已经意识到的那样,Raku 提供了许多可以促进许多不同任务的构造。在这种情况下,我希望以更加程序化的方式表达数字的定义。你可能会完全不同,但你可以放心,Raku 可以让你的工作更轻松,更有乐趣。

嗯……这就是所有人!圣诞节快乐,新年快乐!

comments powered by Disqus