計算プリントを自動生成する その14_3 3数の計算 (答えから問題を変える)
はじめに
前回、ランダムな計算式作成とその計算式を解く関数を作成しました。
今回はその求めた答えを利用して、狙った計算式を作成します。
最初に謝罪
今回のコードはめちゃくちゃ読みにくいです。
力不足なせいで、とにかく形にするだけになってしまいました。
狙った計算式を作成
今まで(その13まで)は演算子によって適切な数値を決めて、答えを求めて、それを計算式にして……
のように行っていました。
「きれいな」やり方です。
それに比べると今回は「きたない」やり方かもしれません。
- とりあえず適当に計算式を作ってみる
- 答えを解く
- 答えが欲しいものと違った場合には計算式を少しいじる
- 2,3を繰り返す
- 答えが適切になったらそれを計算式にする
という手順を踏みます。
今までと全然違うアプローチです。
ランダム作成関数と解析関数はすでに手持ちにあるので、今回は答えの判定とそれによる問題の修正について考えます。
自動でやってみる
Fractionクラス
前回のFractionクラスに不満があったので書き換えます。
Public Va As Integer '分子 Public Vb As Integer '分母 Public str As String '計算式 Private Sub Class_Initialize() Va = 0 Vb = 1 str = "" End Sub Function ConS() '-------------------- '分子と分母からFractionを構成 '-------------------- Call Fit If Va = 0 Then str = "0" ElseIf Vb = 1 Then str = Va Else str = Va & "/" & Vb End If End Function Function ConV() '------------------- '文字式からFractionを構成 '------------------- Dim trgt As String Dim stock As String Dim i As Integer If str = "" Then Exit Function If InStr(str, "/") = 0 Then Va = Val(str) Exit Function End If For i = 1 To Len(str) trgt = Mid(str, i, 1) If trgt = "/" Then Va = Val(stock) stock = "" Else stock = stock & trgt End If Next If stock = "" Then Vb = 1 Else Vb = Val(stock) End If Call ConS End Function Function Fit() '--------------------- '約分 '--------------------- Dim L As Integer '最大公約数 If Va * Vb = 0 Then Exit Function L = Application.WorksheetFunction.Gcd(Abs(Va), Abs(Vb)) Va = Va / L Vb = Vb / L End Function
ちょっと使いやすくなったけども、まだ微妙です。
改善の余地はかなりあり。
Int_Str_Make
前回の「Int_Rnd_Str」をあちこち書き換えました。
Function Int_Str_Make(NN, version) As String '------------------ '変数の定義 '------------------ Dim ope() As String '演算子 Dim num() As Integer '数値 Dim ans As Fraction '数式の値を出す Dim i As Integer '繰り返しの制御変数 Dim box As Variant '演算子決定のアシスト変数 Dim tmp As Integer '演算子決定のアシスト変数 Dim str As String '計算式 Dim cnt As Integer '------------------ 'opeとnumの配列数を指定 '------------------ ReDim ope(1 To NN) ReDim num(1 To NN) '------------------ 'boxの指定 '------------------ box = Array("+", "-", "×", "÷") '------------------ '数式を仮決め '------------------ ReSelect: '------------------ 'opeの値を指定 '------------------ For i = 1 To NN - 1 tmp = Rnd_Num(0, 3) ope(i) = box(tmp) Next '------------------ 'numの値を仮決め '------------------ For i = 1 To NN num(i) = Rnd_Num(1, 9) Next cnt = 0 ReCal: '------------------ 'ループしている場合 '------------------ If cnt > 20 Then str = "" GoTo ReSelect End If '------------------ 'ansを作成 '------------------ For i = 1 To NN str = str & num(i) & ope(i) Next Set ans = Int_Str_Analyze(str) '------------------ '繰り返すかの判定 '------------------ If ans.Va < 0 Then cnt = cnt + 1 str = "" num(1) = num(1) + Abs(ans.Va) \ 4 For i = 1 To NN - 1 Select Case ope(i) Case Is = "+" num(i + 1) = num(i + 1) + 1 Case Is = "-" num(i + 1) = Application.WorksheetFunction.Max(2, num(i + 1) - 1) End Select Next GoTo ReCal End If If ans.Vb > 1 Then cnt = cnt + 1 str = "" num(1) = num(1) * ans.Vb For i = 1 To NN - 1 If ope(i) = "+" Or ope(i) = "-" Then num(i + 1) = num(i + 1) * ans.Vb End If Next GoTo ReCal End If If ans.Va > 100 Then cnt = cnt + 1 str = "" For i = 1 To NN - 1 If ope(i) <> "÷" Then num(i) = Application.WorksheetFunction.Max(2, num(i) \ 2) End If Next num(i) = Application.WorksheetFunction.Max(2, num(i) - ans.Va \ 3) GoTo ReCal End If '------------------ 'strを再構成 '------------------ Debug.Print cnt str = str & "=" & ans.str Int_Str_Make = str End Function
先ほど、今回の制約、で説明した通りのコードです。
Int_Str_Analyze
こちらは前回の解析関数を書き換えたものです。
求めた答えを上の関数で考慮する際の手間を考えて、関数の型を「Fraction」にしました。
本筋とは関係ありませんが、オブジェクト系の関数の扱い方がよくわからなくて苦戦しました。
Function Int_Str_Analyze(ByVal str As String) As Fraction '---------------------- '整数の計算式の答えを求める '---------------------- '---------------------- '変数の定義 '---------------------- Dim num() As Fraction '数字を仕分ける変数 Dim ope() As String '演算子を仕分ける変数 Dim i As Integer '繰り返しの制御変数 Dim trgt As String '仕分けのアシスト変数 Dim stock As String '仕分けのアシスト変数 Dim cnt As Integer '仕分けのアシスト変数 Dim trgt2 As String '計算のアシスト変数 Dim ans As Fraction '計算のアシスト変数 Rtrn: '---------------------- '変数の初期化 '---------------------- cnt = 0 stock = "" trgt = "" trgt2 = "" ReDim num(cnt) ReDim ope(cnt) Set ans = New Fraction '---------------------- 'strをopeとnumに分ける '---------------------- For i = 1 To Len(str) trgt = Mid(str, i, 1) Select Case trgt Case Is = "+", "-", "×", "÷" ReDim Preserve num(cnt) ReDim Preserve ope(cnt) Set num(cnt) = New Fraction num(cnt).str = stock num(cnt).ConV ope(cnt) = trgt stock = "" cnt = cnt + 1 Case Else stock = stock & trgt End Select Next If stock <> "" Then ReDim Preserve num(cnt) Set num(cnt) = New Fraction num(cnt).str = stock num(cnt).ConV stock = "" End If '---------------------- '条件を満たしている場合は終了 '---------------------- If cnt = 0 Then Set ans = New Fraction ans.str = str ans.ConV Set Int_Str_Analyze = ans Exit Function End If '---------------------- '計算を行う '---------------------- trgt2 = "" For i = 0 To UBound(ope) Select Case ope(i) Case "×" trgt2 = num(i).str & ope(i) & num(i + 1).str ans.Va = num(i).Va * num(i + 1).Va ans.Vb = num(i).Vb * num(i + 1).Vb ans.ConS str = Replace(str, trgt2, ans.str, , 1) GoTo Rtrn Case "÷" trgt2 = num(i).str & ope(i) & num(i + 1).str ans.Va = num(i).Va * num(i + 1).Vb ans.Vb = num(i).Vb * num(i + 1).Va ans.ConS str = Replace(str, trgt2, ans.str, , 1) GoTo Rtrn End Select Next For i = 0 To cnt Select Case ope(i) Case "+" trgt2 = num(i).str & ope(i) & num(i + 1).str ans.Va = num(i).Va * num(i + 1).Vb + num(i).Vb * num(i + 1).Va ans.Vb = num(i).Vb * num(i + 1).Vb ans.ConS str = Replace(str, trgt2, ans.str, , 1) GoTo Rtrn Case "-" trgt2 = num(i).str & ope(i) & num(i + 1).str ans.Va = num(i).Va * num(i + 1).Vb - num(i).Vb * num(i + 1).Va ans.Vb = num(i).Vb * num(i + 1).Vb ans.ConS str = Replace(str, trgt2, ans.str, , 1) GoTo Rtrn End Select Next '---------------- '予期せぬ処理の場合はSTOP '---------------- Stop End Function
まとめ
余裕がなかったのでかなりわかりにくい関数たちになってしまいました。
次回にもうちょっと形を整えます。