Quantcast
Channel: 円周率近似値の日に生まれて理系じゃないわけないだろ! - knifeのblog
Viewing all articles
Browse latest Browse all 5376

奇々怪々な小町算 その2

$
0
0

昨日は、いろいろと場合分けをして問題を解きました。



今回は、この問題をアレンジして、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個入る配列を外部変数として、再帰処理でしょうかね。

ただ、いくつかの変数は絞りこめるので、余計なループは事前に枝刈り出来るので、それをやっておくのがベターというかベストでしょうか。

alogbcdefCg×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;
}

まぁ、適当に書いてみたけど、あんまり参考にはしないで、自分の好きなようにコーディングしてね。

先の記事でやった絞り込みは、解が求まってしまったので、これ以上の絞り込みは無しにしておきましょうか。

とりあえず、この記事はここまでにして、
次回は、プログラムが出力した解を発表して終わりしましょうか。


ではでは


Viewing all articles
Browse latest Browse all 5376

Trending Articles