昨日は、いろいろと場合分けをして問題を解きました。
今回は、この問題をアレンジして、C言語ないし、Javascriptで書いたらどうなるのかを考えていこうと思います。
また、検算用にExcelのワークシート関数も交えて書いていきます。
問題
□log□□□□-□C□×□-□=2020
上式は小町算の覆面算です。
小町算は1から9が1つずつ登場する計算式です。
9つの□には異なる1から9までの数字で埋めよ。
logは対数、Cはコンビネーション(組み合わせ)です。
でしたが、これをプログラミングの問題と解釈して、プログラムを書いて解いてみようという試みです。
そこで、プログラミング問題として、
1) |左辺-右辺|<1に該当する解をすべて求めよ。
2) 左辺が自然数になる解をすべて求めよ。
3) 等号が満たされる解をすべて求めよ。
というようにして、先の記事の検証を含めた問題としました。
どれをやっても解は見つかるのですが、多少アプローチは違いますよね。
出力として、
C言語はASCIIテキストでの出力なので、
1^log_2(345)-6C7x8-9=2020.0000000000\n
JavascriptはHTMLを絡めて、innerHTMLで
1<sup>log<sub>2</sub>345</sup>-<sub>6</sub>C<sub>7</sub>×8-9=2020.0000000000<br>
としておきましょうか。
さて、C言語にしろ、Javascriptにしろ、数学の関数で標準的に存在するのは、この式ではpow関数とlog関数くらいでしょうか。
Excelのワークシート関数には、POWER関数、LOG関数、COMBIN関数が揃っていますので、関数に関しては他より楽ですが、全パターンを作り出すのは他よりも面倒ですので、検算用とします。
まずは、冪乗関数から、ab=xを求めたいとします。
C言語において、数学の関数を使うためには、
#include <math.h>
が必要です。
float型 = powf(float型, float型);
double型 = pow(double型, double型);
long double型 = powl(long double型, long double型);
の3種類が用意されているかと思います。
今回は、それほど精度を必要としないので、double型とします。
double a, b, x;
/* a, bに値が入っているとして、xに格納します */
x = pow(a, b);
のようになります。
Javascriptにおいて、数学の関数を使うのには特に準備は要りませんが、数学関連の関数はMathクラスの関数となっております。
var a, b, x;
/* a, bに値が入っているとして、xに格納します */
x = Math.pow(a, b);
のようになります。
Excelのワークシートにおいて、数学の関数を使うのには特に準備は要りません。
A1セルにaの値、B1セルにbの値が入っているとして、
=A1^B1
または、POWER関数を使って、
=POWER(A1,B1)
のようになります。
続いて、対数で、logbc=xを求めたいと思います。
C言語では、対数関連の関数として、double型に限定すると、
double型 = log(double型);
double型 = log10(double型);
の2つは最低でもあるかと思います。
他にもlog1pなどあるかとは思いますが、今回の問題には使わないので割愛します。
前者が自然対数、後者が常用対数です。
今回の問題では、底も設定したいのに、C言語には底も指定出来る対数関数はありません。
そこで、数学の知識を使います。
logbc=logrc/logrb
r>1を満たす実数なので、
double b, c, x;
/* b, cに値が入っているとして、xに格納します。 */
x = log(c)/log(b);
のようにすれば問題は解決されます。
自然対数を使いましたが、分母分子が同じ底であれば良いので、常用対数でも同じ結果となります。
Javascriptの対数の関数は、自然対数のMath.log()関数で、引数は1つだけです。
var b, c, x;
/* b, cに値が入っているとして、xに格納します。 */
x = Math.log(b)/Math.log(c);
のようになります。
Excelワークシート関数の対数関連の関数は、LOG(数値,底)と、底を指定できます。
B1セルにbの値、C1セルにcの値が入っているとして、
=LOG(C1,B1)
のようになります。
最後に、コンビネーション(組み合わせ)で、fCg=xとします。
C言語には、標準関数としてコンビネーションや階乗はありません。
なければ作るしかありません。
というわけで、数学の知識が必要になります。
fCg=f!/(g!×(f-g)!)
となっております。
f!、g!、(f-g)!の3つの階乗を求めて、コンビネーションの計算するというのもありなんですが、
数式で書くと難しく見えてしまいますので、実際に数値を書いてみると難しくありません。
f=9、g=5のとき、
fCg=9C5=(9×8×7×6×5)/(5×4×3×2×1)
なのです。
分子は9から始まって5個分、分子は5から始まって5個分の積の形なのです。
つまり、変数で書くと、分子はfから始まってg個分、分子はgから始まってg個分の積の形なのです。
int i, j;
double f, g, x;
/* d,eに値が入っているとして、fに格納します。 */
x = 1.0;
for (i=(int)f, j=(int)g; j>0; i--,j--) {
x *= (double)i;
x /= (double)j;
}
のようになります。
なぜ、階乗関数を作って、3つを呼び出すということをやらないかというと、階乗はただでさえオーバーフローしやすい関数なのです。
64ビット整数型で作ったとして、20!くらいが限界だと思います。
まぁ、今回の問題では最大でも9!ですから、問題は無いかとは思いますが、多重ネストであるので、出来るだけ余計な計算はさせたくありませんので、このような形がベターかと思います。
JavascriptのMathクラスにも、コンビネーションや階乗はありません。
var i, j, f, g, x;
x = 1.0;
for (i=f, j=g; j>0; i--, j--) {
x *= i;
x /= j;
}
のようになります。
Excelのワークシート関数には、COMBIN関数があります。
F1セルにfの値、G1セルにgの値が格納されているとして、
=COMBIN(F1,G1)
または、FACT関数を使って、
=FACT(F1)/(FACT(G1)*FACT(F1-G1))
のようになります。
さて、一通り部品レベルでの説明をしましたが、どうやって虱潰しに探していくのか。
9個の変数をa,b,c,d,e,f,g,h,iとやって、多重ネストして、同じ値はcontinueして、なんてのは面倒です。
int a, b, c, d, e, f, g, h, i;
for (a=1; a<10; a++)
for (b=1; b<10; b++) {
if ( a==b ) continue;
for (c=1; c<10; c++) {
if ( a==c || b==c ) continue;
for (d=1; d<10; d++) {
if ( a==d || b==d || c==d ) continue;
for (e=1; e<10; e++) {
if ( a==e || b==e || c==e || d==e ) continue;
for (f=1; f<10; f++) {
if ( a==f || b==f || c==f || d==f || e==f ) continue;
for (g=1; g<10; g++) {
if ( a==g || b==g || c==g || d==g || e==g || f==g ) continue;
for (h=1; h<10; h++) {
if ( a==h || b==h || c==h || d==h || e==h || f==h || g==h ) continue;
for (i=0; i<10; i++) {
if ( a==i || b==i || c==i || d==i || e==i || f==i || g==i || h==i ) continue;
/* 処理 */
}
}
}
}
}
}
}
}
}
}
9ネストぐらいなら書けなくもないけれど、今どきのハードの性能、コンパイラの性能であれば、こんなコードは愚の骨頂だろうか。
ハード的にもコンパイラ的にもプアな時代であれば、再帰処理は通常ループよりも遅いし、スタックオーバーフローになる可能性もあってか、こんなコードを書くことはあったかもしれない。
というか、現実に書いたことはある。
現状でも、どうしても高速化をしなければならないのであれば、書くことはあるのかな?
というわけで、n[9]と9個入る配列を外部変数として、再帰処理でしょうかね。
ただ、いくつかの変数は絞りこめるので、余計なループは事前に枝刈り出来るので、それをやっておくのがベターというかベストでしょうか。
alogbcde-fCg×h-i=2020
とすると、
a>1, b>1, f>1, g<f
という4つの変数の絞り込みが出来ますね。
ざっと、C言語でたたき台を書いてみると、
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int n[9];
void next(int m)
{
int i, j;
if ( m < 9 ) {
for (i=1; i<10; i++) {
for (j=0; j<m; j++) {
if ( n[j]==i ) break;
}
if ( j < m ) continue;
n[m] = i;
next(m+1);
}
} else {
/* n[0]^log_n[1](100n[4]+10n[5]+n[6])-n[2]Cn[3]xn[7]-n[8]=2020 */
/* 処理 */
}
}
main()
{
int a, b, f, g;
for (a=2; a<10; a++)
for (b=2; b<10; b++)
if ( a==b ) continue;
for (f=2; f<10; f++) {
if ( a==f || b==f ) continue;
for (g=1; g<f; g++) {
if ( a==g || b==g ) continue;
n[0] = a;
n[1] = b;
n[2] = f;
n[3] = g;
next(4);
}
}
}
return EXIT_SUCCESS;
}
まぁ、適当に書いてみたけど、あんまり参考にはしないで、自分の好きなようにコーディングしてね。
先の記事でやった絞り込みは、解が求まってしまったので、これ以上の絞り込みは無しにしておきましょうか。
とりあえず、この記事はここまでにして、
次回は、プログラムが出力した解を発表して終わりしましょうか。
ではでは