2017年8月30日水曜日

EXCEL VBAで覆面算を解いてみる

今回はEXCEL VBAで覆面算を解くプログラムを作ってみようと思います。
VBAとはVisual Basic for Applicationの略で、プログラミング言語の一種です。
私はVBAを本格的に勉強したこともない素人です。
ネットでちょこちょこ調べて適当に作っているだけです。
VBAのどこがVisualなのかも分かっていません。
こんな私の作るプログラムですので変なところが多々あると思いますがご容赦願います。

覆面算とは、数字が記号や文字に置き換えられた数式から元の数式を求めるパズルです。
例えば、12*12=144という数式において、
1をA、2をB、4をCに置き換えると、AB*AB=ACCとなります。
この式から元の数式を導いてみてくださいという問題ができます。
もちろん異なる数字は異なる記号に置き換えます。
同じ記号は同じ数字、異なる記号は異なる数字を表すことになります。
作り方から明らかだと思いますが、ABはA*Bという意味ではなく2桁の数を表しています。
1桁の数を01,02のように0をつけて2桁で書くことは普通はありませんので、
覆面算においては2桁以上の数の最上位の数は0ではないとしています。

では、AB*AB=ACCという覆面算を解いてみましょう。

Aが2以上だとすると、AB*AB≧AB*20≧A*200>ACC
ですので、A=1と分かります。
10^2=100,11^2=121,12^2=144,13^2=169,14^2=196
AB≧15の場合はAB*AB>200ですので、成立するのはAB=12の場合だけです。

今の場合は答えが一つに決まりましたが、いつでも答えが一つに決まるとは限りません。
複数の答えがある場合もありますし、一つも答えがない場合もあります。
覆面算が与えられたときにすべての答えを求めるプログラムを作ってみたいと思います。

A,B,Cという変数を使って、すべての数の組み合わせについて調べてやればよいです。
FOR~NEXT文を使えば簡単ですね。

FOR A=0 TO 9
FOR B=0 TO 9
FOR C=0 TO 9
正しい式になるかチェック
NEXT C
NEXT B
NEXT A

但し、これだと異なる記号に同じ数字が入ることもありますので、それを除外する必要があります。
また、Aは2桁以上の数の最上位にありますので、A=0の場合も除かないといけません。
IF文を追加してやればいいだけですのでここでは省略します。

正しい式になるかのチェックについて。
ABと書いてもABという名前の変数になってしまうので、覆面算のABの値は計算できません。
ABの値はA*10+Bで計算できます。
ACCの値はA*100+C*10+Cです。
(A*10+B)^2=A*100+C*10+C
が成立する場合のみ、答えを出力すればよいです。

これで一応覆面算を解くプログラムは作れます。
が、覆面算が変わるとそれに合わせてプログラムを変更する必要があります。
特に、使われる記号の数が異なると変更が面倒です。
記号の個数が変わってもプログラムの形がなるべく変わらないようにしたいものです。
そんなときに便利なのが再帰呼び出しです。
再帰呼び出しとは、プログラムや関数が自分自身を呼び出すことです。
プログラミング言語によっては使えないものもありますが、EXCEL VBAでは使えます。

例えば、自然数nに対して階乗n!を返す関数fを考えてみましょう。
引数をnとして、n=1の場合はf(n)=1、n>1の場合はn*f(n-1)としてやればよいです。
具体的には、
Function f(ByVal n As Integer)

If n=1 then
f= 1
else
f=n * f(n-1)
End If

End function

このように定義してやれば、任意のセルに
=f(4)
等と書けば階乗を計算してくれます。
まるで数列の漸化式ですね。
使い方次第で大きく手間を省くことができます。

再帰呼び出しを使ってSEND+MORE=HONEYという覆面算を解くプログラムを作ってみました。

◇◇◇
Const VSU = 9
Public OUTRAW As Integer
Public SU(9) As Integer
Public USE(9) As Integer

Sub SOLVE()
Dim NM As Integer
OUTRAW = 0
Erase USE
VNO = 0
SYORI (VNO)
End Sub


Function SYORI(ByVal VNO As Integer)
If VNO = VSU Then
S = SU(0)
E = SU(1)
N = SU(2)
D = SU(3)
M = SU(4)
O = SU(5)
R = SU(6)
H = SU(7)
Y = SU(8)

SEND = S * 1000 + E * 100 + N * 10 + D
MORE = M * 1000 + O * 100 + R * 10 + E
SAHEN = SEND + MORE
UHEN = H * 10000 + O * 1000 + N * 100 + E * 10 + Y

If SAHEN = UHEN Then
OUTRAW = OUTRAW + 1
Cells(OUTRAW, 1) = SEND
Cells(OUTRAW, 2) = MORE
Cells(OUTRAW, 3) = UHEN
End If

Else
For NM = 0 To 9
If USE(NM) = 0 Then
If NM = 0 And (VNO = 0 Or VNO = 4 OR VNO=7) Then
Else
USE(NM) = 1
SU(VNO) = NM
DUM = SYORI(VNO + 1)
USE(NM) = 0
End If
End If
Next NM
End If
End Function
◇◇◇

実行すると、答えをシートの1行目、2行目、3行目、・・・に順に出力します。
A列にSEND、B列にMORE、C列にHONEYの値を出力します。
今回の覆面算は答えが14個あり、結果は次のようになります。

2453 8094 10547
3452 7094 10546
3569 7085 10654
4235 6092 10327
4268 9352 13620
4705 8367 13072
6235 4092 10327
6723 8547 15270
7452 3094 10546
7569 3085 10654
8453 2094 10547
8705 4367 13072
8723 6547 15270
9268 4352 13620


簡単にプログラムの解説をします。
SUBプロシージャSOLVE内では変数の初期化をして、関数SYORIを呼び出しているだけです。
(変数の初期化は不要だと思いますが念のためしています)
関数SYORI内では引数を+1して再帰呼び出しを行います。
SOLVE→SYORI(0)→SYORI(1)→SYORI(2)→・・・→SYORI(9)
SYORI(n)ではn番の記号の値を0~9に変化させています。
VSUは覆面算に使われている記号の個数。何種類かということです。
今回はS,E,N,D,M,O,R,H,Yの9種類。
この順に0,1,2,・・・,8と番号をつけています。
VNOは何番目の記号かを表します(最初は0)。
配列SU(9)にはそれぞれの記号の値をセットします。
配列USE(9)は数字の重複チェックに使います。
0~9の数字について、未使用であれば0、使用済であれば1をセットします。
S,M,Hは0ではありませんので、VNOが0,4,7のときには値が0になるものを除外しています。
SYORI(9)が呼び出された時点で9個の記号の値が決まっていますので、
その値を使って式が成立するかどうかを判定し、成立する場合には答えを出力します。

すべての組み合わせを調べているだけですので、あまり効率のよいプログラムではありませんが、
私の使っているパソコンで実行すると10秒もかからずに終わりました。
赤字部分を書き変えれば任意の覆面算を解くことができます。
最初に考えたものよりは変更が楽ですが、まだまだ面倒ですね。
SEND+MORE=HONEY
という式だけ書いたら後はすべてプログラムでやってくれるようにしたいです。
次回はこれに挑戦です。

0 件のコメント:

コメントを投稿