[Raku] 如何判斷一個數值是否為精緻質數

作者:   發佈於: ,更新於:   #rakulang

所謂精緻質數,指的是在質數中一個子集合。如果有個數 $n 為精緻質數,那表示若將此數字的任一位數替換為 0..9 中的任一其他數字,所得到的新數字就不是質數了。

比方說 43,將任一位數替換後可得這幾個數所構成的集合:

40, 41, 42, 44, 45, 46, 47, 48, 49, 3, 13, 23, 33, 53, 63, 73, 83, 93

不過,在此集合中 41, 47, 3, 13, 23, 53, 73, 83 這幾個數字都是質數。所以 43 並不是精緻質數。

同理, 41, 47, 3, 13, 23, 53, 73, 83 這幾個質數也都不是精緻質數。

依照其定義,要檢查某數 $x 是否為精緻質數,必須檢查由其生成的眾多其他數。若 $x 為 n 位數字,則需檢查 9n 個其他數。若其他 9n 個數都不是質數的話,則 $x 變為精緻質數。

既然有了這基本想法,以下的 is-delicate-prime 判別函式也就隨之而生了。

sub is-delicate-prime (Int $x --> Bool) {
    # 如果 $x 不是質數,自然也就不是精緻質數
    return False unless $x.is-prime;

    # 由 $x 生成其他 9n 個相關的數,存於 @alt_x 內
    my @digits = $x.Str.flip.comb;
    my @alt_x = ( @digits.keys X (0..9) )
                    .grep(-> ($i, $d) { @digits[$i] != $d })
                    .map(-> ($i, $d) { $x + ($d - @digits[$i]) * 10 ** $i });

    # 若 @alt_x 內的數字都不是質數,那麼 $x 就是精緻質數
    return @alt_x.map(&is-prime).none.so;
}

既然有了判別函數,那麼就可以直接配合 .grep 來做產生器了。比方說可以用如下幾行程式碼來將前 10 個精緻質數印出:

my @delicate-primes = (2..*).hyper.grep(&is-delicate-prime).[^10];

say @delicate-primes;

#=> [294001 505447 584141 604171 971767 1062599 1282529 1524181 2017963 2474431]

精緻質數在自然數中所佔的比例很小,單純用普通的串列配上 .grep 來計算的話多半要讓人等上好一陣子才會有點進展。因此在此使用了 .hyper.grep,讓 raku 自動將運算分散到所有 CPU 上去。

.hyper 大致上來說可加在 .map 前,讓 raku 自動生出夠多執行緒來進行 .map 內的運算。但如果要做的運算過於細小,反而可能會在情景切換與結果合併的階段耗掉許多時間。又、如果運算內容實際上是要彼此共享一些資訊的話,或許會適得其反。使用時機需要好好斟酌。