午後のひとときに、プログラミング問題を解く。
上昇数とは、桁の上位から下位に向かって、前の1桁よりも後ろの1桁のほうが必ず大きくなる自然数で、1桁の自然数はすべて上昇数であり、最大の上昇数は9桁の123456789である。
これを踏まえて、
問題1
上昇数で素数のものをすべて見つけ、個数を示せ。
問題2
問題1を満たした素数で、各桁を逆転させたものも素数であるものもすべて見つけ、個数を示せ。
シンキングタイ~ム
久しぶりのプログラミングの記事ですね。
さて、どうやって解いていくのがベストなのでしょうか。
私の性格上、マシンパワーに頼り切った遅いプログラムは、あまり好みではない。
やれることをやった上で遅いのであれば致し方ないが、コード的に汚くなろうがやれることはやりたいタイプです。
なので、プログラミング演習などでの私のコードに対する評価は低い場合が多いので、演習で高得点を得たい良い子たちは絶対に真似をしないようにw。
さて、プログラミングをする前にやれることは、数学的な考証でしょうか。
上昇数で素数をp、更に桁を逆転させた素数をq、
それぞれの個数をx、yとしましょうか。
pは素数なので、
2より大きな2の倍数、つまり偶数は素数ではないので、奇数だけを素数判定したい。
3より大きな3の倍数は素数ではないことを利用し、
最大の上昇数123456789は各桁の合計が45であり、
3の倍数や9の倍数であることは自明で素数ではないので、
最大で8桁まで調べれば良いことが確定する。
また桁が逆順のqも各桁の和ということは同じなので、3の倍数判定をする必要がないので、こちらは5以降を使って判定したい。
完全な枝刈りは難しいので、上昇数は8桁までということで、8個の変数をネストしたものを採用して、出来得る限り高速化を図る。
これらを盛り込んで、いつものようにC言語で記述する。
joshososu.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
int a, b, c, d, e, f, g, h;
int p, q, r, s;
int x, y;
printf("2\t2\n3\t3\n");
x = y = 2;
for (h=0; h<3; h++)
for (g=h; g<4; g++)
for (f=g; f<5; f++)
for (e=f; e<6; e++)
for (d=e; d<7; d++)
for (c=d; c<8; c++)
for (b=c; b<9; b++)
for (a=b+(b+1)%2; a<10; a+=2) {
p = h*10000000+g*1000000+f*100000+e*10000+d*1000+c*100+b*10+a;
if ( p < 5 ) continue;
s = sqrtl(p)+1;
for (r=3; r<s; r+=2) {
if ( p%r == 0 ) break;
}
if ( p%r == 0 ) continue;
r = p;
while ( r%10 > r%100/10 ) {
r /= 10;
}
if ( r == 0 ) {
printf("%d\t",p);
x++;
q = a*10000000+b*1000000+c*100000+d*10000+e*1000+f*100+g*10+h;
while ( q%10 == 0 ) {
q /= 10;
}
s = sqrtl(q)+1;
for (r=5; r<s; r+=2) {
if ( q%r == 0 ) break;
}
if ( q%2 != 0 && r >= s ) {
printf("%d\n",q);
y++;
} else {
printf("\n");
}
}
}
printf("%d\t%d\n",x,y);
return EXIT_SUCCESS;
}
多重ネストで酷いコードだけど、そんなことよりも、かなりの速度が出ることに気がつくであろう。
> joshososu
2 2
3 3
5 5
7 7
13 31
17 71
19
23
29
37 73
47
59
67
79 97
89
127
137
139
149 941
157 751
167 761
179 971
239
257
269
347 743
349
359 953
367
379
389 983
457
467
479
569
1237 7321
1249 9421
1259 9521
1279 9721
1289
1367
1459
1489
1567
1579
1789 9871
2347
2357
2389
2459
2467
2579
2689
2789
3457
3467 7643
3469 9643
4567
4679
4789
5689
12347
12379
12457
12479
12569
12589
12689 98621
13457 75431
13469 96431
13567
13679
13789 98731
15679 97651
23459
23567
23689
23789
25679
34589 98543
34679
123457
123479
124567
124679
125789
134789
145679
234589
235679
235789
245789
345679
345689 986543
1234789
1235789 9875321
1245689
1456789
12356789
23456789
100 30
素数pを列挙し、pの桁が逆転したqが素数なら同行に列挙。
最終的にp、qの個数を出力して終わり。
これが私の2011年製のボロパソコンでも瞬殺で出力し終わります。
行儀よくご丁寧に、2から素数判定して、上昇数の判定をするようなプログラミングすると、格段に遅くなるのは目に見えていますよね。
どう考えても、素数を上昇数であるかを判定するようなプログラミングでは兎に角遅いだろう。
ボトルネックは、素数判定の回数と、上昇数判定の回数でしょう。
これを出来る限り枝刈りするのが、今回のプログラミングの肝で、上昇数の候補となる数の各桁を別変数でネストして、各桁毎の大小を固定していくことです。
但し、8桁固定で作るため、8桁未満を先頭から0を入るために、前の桁と同じ値になるようになっているので、すべてが上昇数ではありませんが、上昇数になる確率は極めて高いネストになっているのです。
これにより、上昇数判定の回数も減り、同時に素数判定の回数も減ります。
さて、上昇数で素数な数は丁度100個、更に各桁が逆転した素数は30個と、どちらも切りが良いのが不思議ですね。
ではでは