滝の音

滝の音

名こそ流れてなお聞こえけれ

計算プリントを自動生成する その14_3 3数の計算 (答えから問題を変える)

はじめに

前回、ランダムな計算式作成とその計算式を解く関数を作成しました。
今回はその求めた答えを利用して、狙った計算式を作成します。

最初に謝罪

今回のコードはめちゃくちゃ読みにくいです。
力不足なせいで、とにかく形にするだけになってしまいました。

狙った計算式を作成

今まで(その13まで)は演算子によって適切な数値を決めて、答えを求めて、それを計算式にして……
のように行っていました。
「きれいな」やり方です。

それに比べると今回は「きたない」やり方かもしれません。

  1. とりあえず適当に計算式を作ってみる
  2. 答えを解く
  3. 答えが欲しいものと違った場合には計算式を少しいじる
  4. 2,3を繰り返す
  5. 答えが適切になったらそれを計算式にする

という手順を踏みます。
今までと全然違うアプローチです。

ランダム作成関数と解析関数はすでに手持ちにあるので、今回は答えの判定とそれによる問題の修正について考えます。

自動でやってみる

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

まとめ

余裕がなかったのでかなりわかりにくい関数たちになってしまいました。
次回にもうちょっと形を整えます。