滝の音

滝の音

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

計算プリントを自動生成する 文字式への代入

はじめに

記事をためて書いておいて、一週間くらいおきにまとめて投稿しているのですが、なぜかこの回は「はじめに」が空欄でした。
なんでだろう??

コード

Literal2クラス

今回気が付きましたが、コード全体を無造作に載せるより、追加部分や変更部分のみを載せたほうが見やすいですね。

Substitution関数を追加しました。
プライベート変数のelementが役に立っています。

Function Substitution(char, num)
'----------------
'文字に数字を代入
'----------------
 trgt = element(Asc(char) - 96)
  
 Select Case trgt
    Case Is = 0
    Case Is > 0
        Va = Va * num * trgt
        La_ = Replace(La_, char, "")
    Case Is < 0
        Vb = Vb * num * trgt
        Lb_ = Replace(Lb_, char, "")
 End Select
 
 element(Asc(char) - 96) = 0
 
End Function

Lit_Str_Division

文字列を「kou」と「ope」に分割する関数を作りました。
もともとは「Analyze系」の関数内に入れていたのですが、「代入」の関数にも同じ機能が欲しいと思い、サブ関数に分けました。

Function Lit_Str_Divide(str, ByRef kou, ByRef ope)
'----------------------
'strをopeとkouに分ける
'----------------------

'----------------------
'変数の定義
'----------------------
 Dim trgt As String     '仕分けのアシスト変数
 Dim stock As String    '仕分けのアシスト変数
 Dim cnt As Integer     '仕分けのアシスト変数
 
'----------------------
'分割
'----------------------
 For i = 1 To Len(str)
 
    trgt = Mid(str, i, 1)
    Select Case trgt
        Case Is = "+", "-", "×", "÷"
        
            ReDim Preserve kou(cnt)
            ReDim Preserve ope(cnt)
            Set kou(cnt) = New Literal2
            kou(cnt).Conversion (stock)
            ope(cnt) = trgt

            stock = ""
            cnt = cnt + 1
            
        Case Else
            stock = stock & trgt
    End Select
 Next

 If stock <> "" Then
    ReDim Preserve kou(cnt)
    Set kou(cnt) = New Literal2
    kou(cnt).Conversion (stock)
    stock = ""
 End If
 
'-----------------------
'-の処理
'-----------------------
 For i = 0 To UBound(ope)
    If ope(i) = "-" Then
        ope(i) = "+"
        kou(i + 1).Va = kou(i + 1).Va * -1
    End If
 Next
 
End Function

Lit_Str_Substitution

文字列で与えられた文字式について、指定された文字に指定された数値を代入する関数です。

Function Lit_Str_Substitution(ByVal str As String, char As String, num As Integer) As Literal2()

'---------------------
'変数の定義
'---------------------
 Dim kou() As Literal2  '項
 Dim ope() As String    '演算子
 Dim i As Integer       '繰り返しの制御変数
 Dim j As Integer       '繰り返しの制御変数
 
'----------------------
'strをopeとkouに分ける
'----------------------
 Call Lit_Str_Divide(str, kou, ope)

'----------------------
'代入
'----------------------
 For i = LBound(kou) To UBound(kou)
    Call kou(i).Substitution(char, num)
 Next
 
 Lit_Str_Substitution = kou()
End Function

「Analyze」系の関数もそうですが、それ単独でも使える仕様にしているため、「文字列」⇔「kou」の変換が頻繁に起きています。

そうすることで「計算プリントを自動生成する」の本筋からはずれてしまっているのですが、それでもそうしたいなーと思っています。

まとめ

前回、関数の整理をした甲斐もあって、なかなかスムーズに実装できたかなと思います。

とりあえず「文字式の計算」はここで打ち止め。

次に何をしようか迷いますね。

「文章問題」を扱うために「数列」を実装したくて、そのために「文字列の計算」をこなしました。
ただ「文章問題」はわざわざ「数列」を使わなくとも解決できそうなんですよね。

んー。
このあたりでいったん「統合」しておきますか。
いろいろ関数を作りっぱなしになっているので。

計算プリントを生成する 3項以上の文字式の計算 その2(関数を整える)

はじめに

今回は「機能を関数に割り振る」ことをします。

コード

Lit_Formula_Make

前回までの「Lit_Str_Make」です。
ちょっと名前がおかしいと思ったので変更しました。
あと、この関数にいろいろやらせすぎていると感じたので、いくつかの機能をサブ関数に割り振りました。

Function Lit_Formula_Make(NN) As String
'-------------------
'文字式の計算式を作成する
'-------------------

'------------------
'変数の定義
'------------------
 Dim ope() As String    '演算子
 Dim kou() As Literal2   '数値
 Dim i As Integer       '繰り返しの制御変数
 Dim str As String      '計算式
 Dim str1 As String
 Dim str2 As String
 Dim ans()  As Literal2    '計算式の答え
 
'------------------
'kou,opeを指定
'------------------
 Call Lit_kou_ope_Select(NN, kou, ope)

'------------------
'str1を作成
'------------------
 str1 = Lit_Str_Make(kou, ope)
 
'------------------
'ansを配列で取得
'------------------
 ans = Lit_Str_Analyze(str1)
 
'------------------
'str2を作成
'------------------
 ReDim ope(LBound(ans) To UBound(ans))
 
 For i = LBound(ans) To UBound(ans) - 1
    ope(i) = "+"
 Next
 
 str2 = Lit_Str_Make(ans, ope)
 
'------------------
'str2の修飾
'------------------
 If left(str2, 2) = "0+" Then
    str2 = Mid(str2, 3)
 End If
 
 If Right(str2, 1) = "+" Then
    str2 = left(str2, Len(str2) - 1)
 End If
 str2 = Replace(str2, "+-", "-")

'------------------
'戻り値を作成
'------------------
 str = str1 & "=" & str2
 Lit_Formula_Make = str
End Function

これでだいぶ見やすくなったかなと思います。

Lit_kou_ope_Select

「kou」と「ope」の値を決める変数です。
ここが実質的に問題の内容を作っています。

Function Lit_kou_ope_Select(NN, ByRef kou, ByRef ope)
'-----------------
'kou,opeの値を指定
'-----------------

'-----------------
'変数の定義
'-----------------
 Dim box As Variant     '値決定のアシスト変数
 Dim tmp As Integer     '値決定のアシスト変数
 
'------------------
'opeとnumの配列数を指定
'------------------
 ReDim ope(1 To NN)
 ReDim kou(1 To NN)
 
'------------------
'opeの値を指定
'------------------
 box = Array("+", "-", "×", "÷")
 For i = 1 To NN - 1
    tmp = Rnd_Num(0, 1)
    ope(i) = box(tmp)
 Next

'------------------
'kouの値を指定
'------------------
 box = Array("", "a", "b")
 For i = 1 To NN
    Set kou(i) = New Literal2
    kou(i).Va = Rnd_Num(1, 3)
    tmp = Rnd_Num(0, 1)
    kou(i).La = box(tmp)
 Next
 
End Function

Lit_Str_Make

「kou」と「ope」を文字式に起こす関数です。
この機能をサブ関数化することで、個人的にはコードの流れが見やすくなったと思います。

Function Lit_Str_Make(kou, ope)
'-----------------
'kou,opeを文字列に起こす
'-----------------

'-----------------
'変数の定義
'-----------------
 Dim i As Integer   '繰り返しの制御変数
 Dim ans As String  '戻り値
 
 ans = kou(LBound(kou)).S
 
 For i = LBound(kou) + 1 To UBound(kou)
    If Not (kou(i).S = "0" And ope(i - 1) = "+") Then
        
        ans = ans & ope(i - 1) & kou(i).S
    End If
 Next
 
 Lit_Str_Make = ans
End Function

ただ、このコード自体はあまりきれいじゃないです。

Lit_Str_Analyze

関数の型を「Literal2」にして、「kou」の配列を戻り値にしました。
それ以外は変更していないので、コードは省略で。

まとめ

関数の名前の変更とサブ関数の作成をしました。

悪い癖かもしれませんが、「名前」がしっくりこないと気分が悪くなってしまいます。
そういうこだわりはまだ持たなくていい、と思いつつも取り払えないですね。

「修飾」の部分を上手に書けないせいで、全体的にぎこちない見た目になっています。
これはもうちょっとスキルを上げないと解決できなさそうなのでながーく保留。

さて、これで「3項以上の文字式の計算」を一段落です。

次は文字式への「代入」を行います。

計算プリントを自動生成する 3項以上の文字式の計算 その1 (とりあえず作る)

はじめに

基本的には「3数の計算」と同じ考え方です。
以前に「3数の計算」と書いてしまいましたが、「3項の計算」としたほうが良かったですね。。

文字式の計算の特徴

関数を書くときに考慮した文字式の計算の特徴は以下の通りです。

足し合わせない関係の項もある。
答えが多項式になることもある。
答えの多項式の並びを気にする必要がある。

特に答えが多項式になりうるところが厄介ですね。

自動でやってみる

Literal2クラス

新たに「rank」変数を加えました。
項の並び替えに使っています。

Public Va As Integer
Public Vb As Integer
Private La_ As String
Private Lb_ As String

Private element(1 To 26) As Integer

Private Sub Class_Initialize()
 Va = 0
 Vb = 1
End Sub

Property Get rank() As Long
'------------------
'sortで用いる変数を作成
'------------------

 Dim i As Integer
 Dim ans As Long
 
 
 For i = 1 To 3
    ans = ans + element(i) * 10 ^ (3 - i)
 Next
 
 rank = ans
End Property


Function Conversion(str)
'-------------------
'文字列からLiteral2を構成
'-------------------

'-------------------
'変数の定義
'-------------------
 Dim ans(1 To 2) As String
 Dim tmp As Integer
 
'-------------------
'分子と分母に分割
'-------------------
 tmp = InStr(str, "/")
 If tmp <> 0 Then
    ans(1) = Mid(str, 1, tmp - 1)
    ans(2) = Mid(str, tmp + 1)
 Else
    ans(1) = str
    ans(2) = ""
 End If
 
'-------------------
'分子の取得
'-------------------
 trgt = ans(1)
 tmp = Val(trgt)
 If tmp <> 0 Then
    Va = tmp
    La = Replace(trgt, tmp, "", , 1)
 Else
    If Mid(trgt, 1, 1) = "-" Then
        Va = -1
        La = Replace(trgt, "-", "", , 1)
    Else
        Va = 1
        La = trgt
    End If
 End If

'-------------------
'分母の取得
'-------------------
 trgt = ans(2)
 If trgt = "" Then
    Vb = 1
    Exit Function
 End If
 tmp = Val(trgt)
 If tmp <> 0 Then
    Vb = tmp
    Lb = Replace(trgt, tmp, "", , 1)
 Else
    If Mid(trgt, 1, 1) = "-" Then
        Vb = -1
        Lb = Replace(trgt, "-", "", , 1)
    Else
        Vb = 1
        Lb = trgt
    End If
 End If
 
 Call Fit
 
End Function

Property Get La() As String
 La = La_
End Property

Property Let La(str As String)
 La_ = str

 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'element作成のアシスト変数
 Dim num As Integer     'element作成のアシスト変数
 
 
'---------------
'elementの初期化
'---------------
 For i = 1 To 26
    element(i) = 0
 Next
'---------------
'elementに仕分け
'---------------

 For i = 1 To Len(str)
    trgt = Mid(str, i, 1)
    num = Asc(trgt) - 96
    Select Case num
        Case Is < 1
            Stop
        Case Is < 27
            
            element(num) = element(num) + 1
        Case Else
            Stop
    End Select
 Next
 
End Property

Property Get Lb() As String
 Lb = Lb_
End Property

Property Let Lb(str As String)
 Lb_ = str
 
 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'element作成のアシスト変数
 Dim num As Integer     'element作成のアシスト変数
 
'---------------
'elementに仕分け
'---------------
 For i = 1 To Len(str)
    trgt = Mid(str, i, 1)
    num = Asc(trgt) - 96
    Select Case num
        Case Is < 1
            Stop
        Case Is < 27
            
            element(num) = element(num) - 1
        Case Else
            Stop
    End Select
 Next
 
End Property
Function Fit()
'--------------
'整形
'--------------

'--------------
'変数の定義
'--------------
 Dim i As Integer       '繰り返しの制御変数
 Dim L As Integer       '約分のアシスト変数
 Dim ansa As String     '文字分配のアシスト変数
 Dim ansb As String     '文字分配のアシスト変数
 Dim trgt As String     '文字分配のアシスト変数
 
'--------------
'正負の考慮
'--------------
 If Vb < 0 Then
    Va = Va * -1
    Vb = Vb * -1
 End If
 
'--------------
'約分
'--------------
If Va = 0 Then
    Exit Function
 End If
 
 L = Application.WorksheetFunction.Gcd(Abs(Va), Abs(Vb))
 Va = Va / L
 Vb = Vb / L
 
'---------------
'文字の分配
'---------------

 For i = 1 To 26
    trgt = Chr(96 + i)
    Select Case element(i)
        Case Is < -1
            ansb = ansb & trgt & "^" & Abs(element(i))
        Case Is = -1
            ansb = ansb & trgt
        Case Is = 1
            ansa = ansa & trgt
        Case Is > 1
            ansa = ansa & trgt & "^" & element(i)
    End Select
 Next
 La_ = ansa
 Lb_ = ansb
End Function


Property Get S() As String
'-----------------
'V,L_からSを作成
'-----------------

'-----------------
'変数の定義
'-----------------
 Dim ansa As String     '分子
 Dim ansb As String     '分母
 
 Call Fit
 
 Select Case True
    Case La_ = ""
        ansa = Va
    Case Va = 0
        ansa = ""
    Case Va = 1
        ansa = La_
    Case Va = -1
        ansa = "-" & La_
    Case Else
        ansa = Va & La_
 End Select
 
 Select Case True
    Case Lb_ = ""
        If Vb = 1 Then
            ansb = ""
        Else
            ansb = Vb
        End If
    Case Vb = 0
        Stop
    Case Vb = 1
        ansb = Lb_
    Case Vb = -1
        ansb = "-" & Lb_
    Case Else
        ansb = Vb & Lb_
 End Select
 
 If ansa = "" Then
    S = 0
 ElseIf ansb = "" Then
    S = ansa
 Else
    S = ansa & "/" & ansb
 End If
 
 
 
End Property

実は「rank」変数には大きな欠点があり、現状では「a,b,c」の三つの文字しか考慮できません。
ここは保留で。

Lit_Str_Make

以前は2を添えていましたが、今回から外します。

Function Lit_Str_Make(NN) As String
'-------------------
'文字式の計算式を作成する
'-------------------

'------------------
'変数の定義
'------------------
 Dim ope() As String    '演算子
 Dim kou() As Literal2   '数値
 Dim i As Integer       '繰り返しの制御変数
 Dim box As Variant     '演算子決定のアシスト変数
 Dim tmp As Integer     '演算子決定のアシスト変数
 Dim str As String      '計算式
 Dim ans As String      '計算式の答え
 
'------------------
'opeとnumの配列数を指定
'------------------
 ReDim ope(1 To NN)
 ReDim kou(1 To NN)
 
 
'------------------
'opeの値を指定
'------------------
 box = Array("+", "-", "×", "÷")
 For i = 1 To NN - 1
    tmp = Rnd_Num(0, 1)
    ope(i) = box(tmp)
 Next

'------------------
'kouの値を指定
'------------------
 box = Array("", "a", "b")
 For i = 1 To NN
    Set kou(i) = New Literal2
    kou(i).Va = Rnd_Num(1, 3)
    tmp = Rnd_Num(0, 1)
    kou(i).La = box(tmp)
 Next

'------------------
'strを作成
'------------------
 For i = 1 To NN
    str = str & kou(i).S & ope(i)
 Next
'------------------
'ansを計算
'------------------
 ans = Lit_Str_Analyze(str)

'------------------
'戻り値を作成
'------------------
 str = str & "=" & ans
 Lit_Str_Make = str
End Function

Lit_Str_Analyze

整数系に倣って、今回も解析関数を作りました。
項のソート、足し算の処理の仕方、あたりが特徴です。

ソートはよく知らないので、(たぶん)クイックソートにしました。

Function Lit_Str_Analyze(ByVal str As String) As String
'---------------------
'文字式の計算
'---------------------
 
'---------------------
'変数の定義
'---------------------
 Dim kou() As Literal2  '項
 Dim ope() As String    '演算子
 Dim i As Integer       '繰り返しの制御変数
 Dim j As Integer       '繰り返しの制御変数
 Dim trgt As String     '仕分けのアシスト変数
 Dim stock As String    '仕分けのアシスト変数
 Dim cnt As Integer     '仕分けのアシスト変数
 Dim swap As Literal2   'ソートのアシスト変数
 
'----------------------
'strをopeとnumに分ける
'----------------------
 For i = 1 To Len(str)
 
    trgt = Mid(str, i, 1)
    Select Case trgt
        Case Is = "+", "-", "×", "÷"
        
            ReDim Preserve kou(cnt)
            ReDim Preserve ope(cnt)
            Set kou(cnt) = New Literal2
            kou(cnt).Conversion (stock)
            ope(cnt) = trgt

            stock = ""
            cnt = cnt + 1
            
        Case Else
            stock = stock & trgt
    End Select
 Next

 If stock <> "" Then
    ReDim Preserve kou(cnt)
    Set kou(cnt) = New Literal2
    kou(cnt).Conversion (stock)
    stock = ""
 End If
 
'-----------------------
'-の処理
'-----------------------
 For i = 0 To UBound(ope)
    If ope(i) = "-" Then
        ope(i) = "+"
        kou(i + 1).Va = kou(i + 1).Va * -1
    End If
 Next
'-----------------------
'計算を行う
'-----------------------
Rtrn:
 
 '掛け算と割り算
  For i = 0 To UBound(kou) - 1
    Select Case ope(i)
        Case Is = "×"
            kou(i).Va = kou(i).Va * kou(i + 1).Va
            kou(i).Vb = kou(i).Vb * kou(i + 1).Vb
            kou(i).La = kou(i).La & kou(i + 1).La
            kou(i).Lb = kou(i).Lb & kou(i + 1).Lb
            Call V_Shift(ope, i)
            Call O_Shift(kou, i + 1)
            GoTo Rtrn
        Case Is = "÷"
            kou(i).Va = kou(i).Va * kou(i + 1).Vb
            kou(i).Vb = kou(i).Vb * kou(i + 1).Va
            kou(i).La = kou(i).La & kou(i + 1).Lb
            kou(i).Lb = kou(i).Lb & kou(i + 1).La
            Call V_Shift(ope, i)
            Call O_Shift(kou, i + 1)
            GoTo Rtrn
    End Select
 Next
 
 '項をソートする
 Set swap = New Literal2
 
 For i = 0 To UBound(kou) - 1
    For j = i To UBound(kou)
        If kou(i).rank < kou(j).rank Then
            Set swap = kou(i)
            Set kou(i) = kou(j)
            Set kou(j) = swap
        End If
    Next
 Next
 
Rtrn2:
 
 '足し算と引き算
 For i = 0 To UBound(kou) - 1
    
    If kou(i).La = kou(i + 1).La And kou(i).Lb = kou(i + 1).Lb Then
        kou(i).Va = kou(i).Va * kou(i + 1).Vb + kou(i).Vb * kou(i + 1).Va
        kou(i).Vb = kou(i).Vb * kou(i + 1).Vb
        Call V_Shift(ope, i)
        Call O_Shift(kou, i + 1)
        GoTo Rtrn2
    End If

 Next
 
'----------------------
'戻り値の作成
'----------------------
 kou(0).Fit
 ans = kou(0).S
 
 If ope(0) <> "END" Then
    For i = 0 To UBound(ope)
        kou(i + 1).Fit
        If kou(i + 1).Va <> 0 Then
            ans = ans & ope(i) & kou(i + 1).S
        End If
    Next
    
 End If
 
'--------------------
'特殊な処理
'--------------------
 If Len(ans) > 1 Then
 
    If left(ans, 2) = "0+" Then
        ans = Mid(ans, 3)
    End If
 End If
 
 ans = Replace(ans, "+-", "-")
 ans = Replace(ans, "--", "+")


 Lit_Str_Analyze = ans

End Function

「特殊な処理」のところは正直言って甘えです。
良い解決策が見つかったから消したいですね。

まとめ

とりあえず作れはしました。
現状だと「Str_Make」の関数に機能をいろいろつけすぎだと思うので、次回にちょっと整えます。

計算プリントを生成する 文字式の計算 その2(2aと3a^2の四則の計算)

はじめに

今回は2aと3a2の四則の計算を行います。
ちょっとややこしいところです。

手動でやってみる

文章題で思いましたが、手動でやってみることって大事ですね。
癖が見えてきます。

計算式 答え
2a+3a2 2a+3a2
2a-3a2 2a-3a2
2a×3a2 6a3
2a÷3a2 2/3a

難解な点が二か所。
答えが多項式になるところ(足し算と引き算)と
答えが分数になり分母に文字が来るところ(割り算)です。

この2つをクリアすることが今回の課題です。

自動でやってみる

正直まだ理解しきれていないところがあって、とにかくコードが複雑です。

Literal2クラス

分数にも対応しているLiteralクラスです。
一応Literalクラスと別に作成しました。

Public Va As Integer
Public Vb As Integer
Private La_ As String
Private Lb_ As String

Private element(1 To 26) As Integer

Private Sub Class_Initialize()
 Va = 0
 Vb = 1
End Sub

Property Get La() As String
 La = La_
End Property

Property Let La(str As String)
 La_ = str

 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'element作成のアシスト変数
 Dim num As Integer     'element作成のアシスト変数
 
'---------------
'elementに仕分け
'---------------
 For i = 1 To Len(str)
    trgt = Mid(str, i, 1)
    num = Asc(trgt) - 96
    Select Case num
        Case Is < 1
            Stop
        Case Is < 27
            
            element(num) = element(num) + 1
        Case Else
            Stop
    End Select
 Next
 
End Property

Property Get Lb() As String
 Lb = Lb_
End Property

Property Let Lb(str As String)
 Lb_ = str
 
 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'element作成のアシスト変数
 Dim num As Integer     'element作成のアシスト変数
 
'---------------
'elementに仕分け
'---------------
 For i = 1 To Len(str)
    trgt = Mid(str, i, 1)
    num = Asc(trgt) - 96
    Select Case num
        Case Is < 1
            Stop
        Case Is < 27
            
            element(num) = element(num) - 1
        Case Else
            Stop
    End Select
 Next
 
End Property
Function Fit()
'--------------
'整形
'--------------

'--------------
'変数の定義
'--------------
 Dim i As Integer       '繰り返しの制御変数
 Dim L As Integer       '約分のアシスト変数
 Dim ansa As String     '文字分配のアシスト変数
 Dim ansb As String     '文字分配のアシスト変数
 Dim trgt As String     '文字分配のアシスト変数
 
'--------------
'正負の考慮
'--------------
 If Vb < 0 Then
    Va = Va * -1
    Vb = Vb * -1
 End If
 
'--------------
'約分
'--------------
If Va = 0 Then
    Exit Function
 End If
 
 L = Application.WorksheetFunction.Gcd(Abs(Va), Abs(Vb))
 Va = Va / L
 Vb = Vb / L
 
'---------------
'文字の分配
'---------------

 For i = 1 To 26
    trgt = Chr(96 + i)
    Select Case element(i)
        Case Is < -1
            ansb = ansb & trgt & "^" & Abs(element(i))
        Case Is = -1
            ansb = ansb & trgt
        Case Is = 1
            ansa = ansa & trgt
        Case Is > 1
            ansa = ansa & trgt & "^" & element(i)
    End Select
 Next
 La_ = ansa
 Lb_ = ansb
End Function


Property Get S() As String
'-----------------
'V,L_からSを作成
'-----------------

'-----------------
'変数の定義
'-----------------
 Dim ansa As String     '分子
 Dim ansb As String     '分母
 
 Call Fit
 
 Select Case True
    Case La_ = ""
        ansa = Va
    Case Va = 0
        ansa = ""
    Case Va = 1
        ansa = La_
    Case Va = -1
        ansa = "-" & La_
    Case Else
        ansa = Va & La_
 End Select
 
 Select Case True
    Case Lb_ = ""
        If Vb = 1 Then
            ansb = ""
        Else
            ansb = Vb
        End If
    Case Vb = 0
        Stop
    Case Vb = 1
        ansb = Lb_
    Case Vb = -1
        ansb = "-" & Lb_
    Case Else
        ansb = Vb & Lb_
 End Select
 
 If ansa = "" Then
    S = 0
 ElseIf ansb = "" Then
    S = ansa
 Else
    S = ansa & "/" & ansb
 End If
 
End Property

Lit_Str_Make_2

Literal2クラスに対応した関数です。

Function Lit_Str_Make_2(NN) As String
'-------------------
'文字式の計算式を作成する
'-------------------

'------------------
'変数の定義
'------------------
 Dim ope() As String    '演算子
 Dim kou() As Literal2   '数値
 Dim i As Integer       '繰り返しの制御変数
 Dim box As Variant     '演算子決定のアシスト変数
 Dim tmp As Integer     '演算子決定のアシスト変数
 Dim str As String      '計算式
 Dim ans As String      '計算式の答え
 
'------------------
'opeとnumの配列数を指定
'------------------
 ReDim ope(1 To NN)
 ReDim kou(1 To NN)

'------------------
'boxの指定
'------------------
 box = Array("+", "-", "×", "÷")
 
'------------------
'opeの値を指定
'------------------
 For i = 1 To NN - 1
    tmp = Rnd_Num(0, 3)
    ope(i) = box(tmp)
 Next
 ope(2) = "="
'------------------
'kouの値を指定
'------------------
 Set kou(1) = New Literal2
 kou(1).Va = 2
 kou(1).La = "a"
 
 Set kou(2) = New Literal2
 kou(2).Va = 3
 kou(2).La = "aa"
 
'------------------
'ansを計算
'------------------
 ans = Lit_Calculation_2(kou, ope)

'------------------
'ansを作成
'------------------
 For i = 1 To NN
    str = str & kou(i).S & ope(i)
 Next
 
 str = str & ans
 Lit_Str_Make_2 = str
End Function

Lit_Calculation_2

こちらもLiteral2クラス対応の関数。
2項の計算専用にしているため、あまり汎用性はありません。

Function Lit_Calculation_2(ByVal kou, ope) As String
'------------------
'2項の文字式の計算
'------------------
 Dim ans As Literal2
 Dim ans2 As String
 Set ans = New Literal2
 
 Select Case ope(1)
    Case Is = "+"
        If kou(1).La = kou(2).La And kou(1).Lb = kou(2).Lb Then
            ans.Va = kou(1).Va * kou(2).Vb + kou(1).Vb * kou(2).Va
            ans.Vb = kou(1).Vb * kou(2).Vb
            ans.La = kou(1).La
            ans.Lb = kou(1).Lb
            
        Else
            ans2 = kou(1).S & ope(1) & kou(2).S
            Lit_Calculation_2 = ans2
            Exit Function
        End If
        
    Case Is = "-"
        If kou(1).La = kou(2).La And kou(1).Lb = kou(2).Lb Then
            ans.Va = kou(1).Va * kou(2).Vb - kou(1).Vb * kou(2).Va
            ans.Vb = kou(1).Vb * kou(2).Vb
            ans.La = kou(1).La
            ans.Lb = kou(1).Lb
        Else
            ans2 = kou(1).S & ope(1) & kou(2).S
            Lit_Calculation_2 = ans2
            Exit Function
        End If
        
    Case Is = "×"
        ans.Va = kou(1).Va * kou(2).Va
        ans.Vb = kou(1).Vb * kou(2).Vb
        ans.La = kou(1).La & kou(2).La
        ans.Lb = kou(1).Lb & kou(2).Lb
    Case Is = "÷"
            ans.Va = kou(1).Va * kou(2).Vb
            ans.Vb = kou(1).Vb * kou(2).Va
            ans.La = kou(1).La & kou(2).Lb
            ans.Lb = kou(1).Lb & kou(2).La
 End Select
 
 Lit_Calculation_2 = ans.S
 
End Function```  

ここはかなり汎用性を削っています。  
それでも2項の計算ならできるので良しとします。  

できる範囲でとにかく作る。が大事だと思っています。  

###まとめ  

これでとりあえず2a,3a^2の四則の計算はクリアです。  
ついでにどんな2項の計算でも(たぶん)できるようになりました。  

次にやることは迷いますね。  
右の道は楽の看板。  
文字式への代入です。  
左の道は覚悟の看板。  
3項以上の文字式の計算です。  

んー。  
今勢いに乗っているので、3項以上の文字式の計算をやりましょう。  
無理そうだったらできるところまでで凍結しますが、現時点で出来るところまでやりましょう。  

計算プリントを生成する 文字式の計算 その1(2aとaの四則の計算)

はじめに

想像より早く「計算プリント」に戻ってきました。
今回は項をa,2aに絞って、それらの四則の計算を行います。

あとタイトルのナンバリングについて。
今まで通りなら15_1なのですが、それってわかりやすいか?と思い、ちょっと変更しました。

手動でやってみる

計算式 答え
2a+a 3a
2a-a a
2a×a 2a2
2a÷a 2

これを見るだけでも文字式に必要そうな要素は見えてきますね。

自動でやってみる

Literalクラス

分数と同様にクラスを作ります。

Public V As Integer
Private L_ As String
Private element(1 To 26) As Integer

Private Sub Class_Initialize()
 V = 0
End Sub

Property Get L() As String
 L = L_
End Property

Property Let L(str As String)
 L_ = str
 
 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'element作成のアシスト変数
 Dim num As Integer     'element作成のアシスト変数
 
'---------------
'elementに仕分け
'---------------
 For i = 1 To Len(str)
    trgt = Mid(str, i, 1)
    num = Asc(trgt) - 96
    Select Case num
        Case Is < 1
            Stop
        Case Is < 27
            
            element(num) = element(num) + 1
        Case Else
            Stop
    End Select
 Next
 
 
End Property

Function Fit()
'--------------
'L_の表記を整える

'--------------
'変数の定義
'--------------
 Dim i As Integer       '繰り返しの制御変数
 Dim trgt As String     'fitのアシスト変数
 Dim num As Integer     'fitのアシスト変数
 Dim asn As String      'fitのアシスト変数
'---------------
'elementの反映
'---------------

 For i = 1 To Len(L_)
    trgt = Chr(i + 96)
    num = element(i)
    Select Case num
        Case Is = 0
        Case Is = 1
            ans = ans & trgt
        Case Else
            ans = ans & trgt & "^" & num
    End Select
 Next
 
 L_ = ans
End Function



Property Get S() As String
'-----------------
'V,L_からStrを作成
'-----------------

'-----------------
'考慮が必要ない場合の対処
'-----------------

 If L_ = "" Then
    S = V
    Exit Property
 End If
 

 Select Case True
    Case V = 0
        S = 0
    Case V = 1
        S = L_
    Case V = -1
        S = "-" & L_
    Case Else
        S = V & L_
 End Select
 
End Property

「element」が特徴かなと思います。

Lit_Str_Make

「Int_Str_Make」を参考にしました、サブ関数がまだ充実していないのでかなり機能は制限されています。

Function Lit_Str_Make(NN) As String
'-------------------
'文字式の計算式を作成する
'-------------------

'------------------
'変数の定義
'------------------
 Dim ope() As String    '演算子
 Dim kou() As Literal   '数値
 Dim i As Integer       '繰り返しの制御変数
 Dim box As Variant     '演算子決定のアシスト変数
 Dim tmp As Integer     '演算子決定のアシスト変数
 Dim str As String      '計算式
 
'------------------
'opeとnumの配列数を指定
'------------------
 ReDim ope(1 To NN + 1)
 ReDim kou(1 To NN + 1)

'------------------
'boxの指定
'------------------
 box = Array("+", "-", "×", "÷")
 
'------------------
'opeの値を指定
'------------------
 For i = 1 To NN - 1
    tmp = Rnd_Num(0, 3)
    ope(i) = box(tmp)
 Next
 ope(2) = "="
'------------------
'kouの値を指定
'------------------
 For i = 1 To NN
    Set kou(i) = New Literal
    kou(i).V = Rnd_Num(1, 9)
    kou(i).L = "a"
 Next

 kou(1).V = 2
 kou(2).V = 1
 
'------------------
'kou3を計算
'------------------

 Set kou(3) = New Literal
 Select Case ope(1)
    Case Is = "+"
        If kou(1).L = kou(2).L Then
            kou(3).V = kou(1).V + kou(2).V
            kou(3).L = kou(1).L
        End If
    Case Is = "-"
        If kou(1).L = kou(2).L Then
            kou(3).V = kou(1).V - kou(2).V
            kou(3).L = kou(1).L
        End If
    Case Is = "×"
        kou(3).V = kou(1).V * kou(2).V
        kou(3).L = kou(1).L & kou(2).L
    Case Is = "÷"
        If kou(1).L = kou(2).L Then
            kou(3).V = kou(1).V / kou(2).V
            kou(3).L = ""
        End If
 End Select
 
 kou(3).Fit
'------------------
'ansを作成
'------------------
 For i = 1 To NN + 1
    str = str & kou(i).S & ope(i)
 Next
 
 Lit_Str_Make = str
End Function

まとめ

今回は項を2a,aに設定することで、難しい問題をいくつか回避しています。
次回は項を2a,3a2に設定して同じことを行います。

文章問題を生成する その3_4 ひながた (計算をする)

はじめに

ひとつのコーナーが4回続くのは初めてかもしれません。
だんだん枷は外れてきていますが、サブ関数をあまり設置していないので、コードが長いですね。

均したい部分

まだ均せていない部分は以下の通り。

「num1」「num2」の値の範囲を変更した。
「num3」の求め方を変えた。

今回は「num3」を自動で取得できるようにします。

自動でやってみる

今回はかなり簡単。
ほんとうは簡単ではないのですが、遺産があるので。
「解析関数」を使います。
「解析関数」を使いやすい環境を設定することが今回のミッション。

コード

Sub 文章題生成_3_2()
'--------------------------
'文章第の一部を書き換える
'--------------------------

'--------------------------
'変数の定義
'--------------------------
 Dim str() As String        '文章
 Dim path As String         'メモ帳のアドレス
 Dim worlist As Variant     'ワード群の選択
 Dim box() As String        'ワードの候補
 Dim wor() As String        'ワード入れ替え用の変数
 Dim num() As Integer       '数値入れ替え用の変数
 Dim sss As String          '計算式
 Dim ans As Fraction        '計算式の答え
 Dim trgt As String         '数値入れ替え用の変数
 Dim tmp As Integer         '一時使用
 Dim i As Integer           '繰り返しの制御変数
 Dim j As Integer           '繰り返しの制御変数
 
'--------------------------
'文章の読み込み
'--------------------------
 path = ThisWorkbook.path & "\text\text1_1.txt"
 str = txt_input(path)

'--------------------------
'worlistの作成
'--------------------------
 worlist = Split(str(0), " ")
 
'--------------------------
'worとnumの配列数を取得
'--------------------------
 tmp = Val(worlist(0))
 ReDim wor(1 To tmp)
 
 tmp = Val(str(1))
 ReDim num(1 To tmp)
 
'--------------------------
'worを指定
'--------------------------
 For i = 1 To UBound(worlist)
    path = ThisWorkbook.path & "\wor\" & worlist(i) & ".txt"
    box = txt_input(path)
    tmp = Rnd_Num(0, UBound(box))
    wor(i) = box(tmp)
 Next
 
'--------------------------
'numを指定
'--------------------------
 num(1) = Rnd_Num(1, 5) * 10 + 100
 num(2) = Rnd_Num(3, 7)
 
 sss = str(2)
 For i = 1 To UBound(num) - 1
    sss = Replace(sss, "num" & i, num(i))
 Next
 Set ans = New Fraction
 Set ans = Int_Str_Analyze(sss)
 
 num(3) = Val(ans.S)
 
'--------------------------
'問題文を変更
'--------------------------
 For i = LBound(str) + 3 To UBound(str)
    For j = 1 To Val(str(0))
        trgt = "wor" & j
        str(i) = Replace(str(i), trgt, wor(j))
        str(i) = Replace(str(i), trgt, wor(j))
    Next
    For j = 1 To Val(str(1))
        trgt = "num" & j
        str(i) = Replace(str(i), trgt, num(j))
        str(i) = Replace(str(i), trgt, num(j))
    Next
 Next
 
 
'--------------------------
'文章の書き出し
'--------------------------
 For i = LBound(str) + 3 To UBound(str)
    Call Insert_Equation(20, 30 * (i + 1), str(i))
 Next
 
 path = ThisWorkbook.path & "\text\text1_2.txt"
 Call txt_output(str, path)
End Sub

たぶん最適ではないのですが、こんな感じにしましょう。

「num1」「num2」の設定をいじれない理由

言い訳コーナーです笑
言い訳をして、それをつぶすことにしましょう笑

残る枷はnum1,2の設定のみなのですが、ちょっともやもやしています。

数字の範囲を選択することが難しい理由は「刻み」です。

たとえば果物ひとつの値段。
100~150からランダムに数字を選択、は結構簡単なのですが。
実際は110,120,130,140,150のどれかから欲しいですよね。
このような10刻み。
コードに起こせないことはないのですが、これってあるものに似ていますよね。
そう、数列です。

等差数列です。

ここで無理やりコードを書くよりは、数列の環境を整えて、そこで作られた関数を使いたいなーと思っているのです。

ただ数列には文字が出てくるから、それをやるためには文字式の計算(具体的には代入)を実装しないといけないんだよなーとも思っているのです。

。。。

言い訳はこんな感じです。
書いたら結論は出ました。

文章問題はここで一回凍結。
数列の計算プリントを作成して、その過程で等差数列を扱う。
そこでつくるであろう関数を用いてnum1.2の設定をする。
そのためにまずは文字式の計算を実装する。

こうします。

まとめ

まさかの文章問題の凍結です笑
まだ一週間分くらいしかやっていませんが笑

でもどうしても気が変わってしまったので、それに従います。

というわけで次回からまた「計算プリント」に復帰します。
扱う内容は「文字式」。
文字式は面倒くさいなーと思って放置していたのですが。
必要性を感じてすごくやる気が出てきました笑
不思議ですね笑

文章問題を生成する その3_2 ひながた (worのストック)

はじめに

地味な回が続きます。
書くのは意外と難しいのですが。

均したい部分

まだ均せていない部分は以下の通り。

「wor1」を書き換える候補を手書きで加えた。
「wor2」を新たに加えた。 「num1」「num2」の値の範囲を変更した。
「num3」の求め方を変えた。

今回は「wor」のストックを作ります。

worのストック

ちょっとまえに「文章をコード内で管理するのはよくない」と書きました。
「wor」でも同じ対応をとります。
「wor」の候補たちをメモ帳で管理しておく、というやり方です。

今回はフルーツのファイル、人名のファイルを作ります。
ただ「fruit.txt」にフルーツの名前を列挙するだけですが笑
人名も同様に。

そしてまた「text」のほうも一部書き換えます。

自動でやってみる

扱う文章

2 name fruit
3
'--------------------------
'問題文
'--------------------------
wor1はwor2をnum1つもっています。
いま、さらにnum2つのwor2をもらいました。
もっているwor2はぜんぶでいくつでしょうか。

'--------------------------
'解き方
'--------------------------
num1+num2=num3
よってnum3つ

コード

先ほどの考えを踏まえてコードを書き換えます。
前回は「文章題生成3」を変えたので、今回は「文章題生成4」を変えます。
もちろん前回の変更点は踏んでいます。

Sub 文章題生成_4_1()
'--------------------------
'文章第の一部を書き換える
'--------------------------

'--------------------------
'変数の定義
'--------------------------
 Dim str() As String        '文章
 Dim path As String         'メモ帳のアドレス
 Dim worlist As Variant     'ワード群の選択
 Dim box() As String        'ワードの候補
 Dim wor() As String        'ワード入れ替え用の変数
 Dim num() As Integer       '数値入れ替え用の変数
 Dim trgt As String         '数値入れ替え用の変数
 Dim tmp As Integer         '一時使用
 Dim i As Integer           '繰り返しの制御変数
 Dim j As Integer           '繰り返しの制御変数
 
'--------------------------
'文章の読み込み
'--------------------------
 path = ThisWorkbook.path & "\text\text2_1.txt"
 str = txt_input(path)

'--------------------------
'worlistの作成
'--------------------------
 worlist = Split(str(0), " ")
 
'--------------------------
'worとnumの配列数を取得
'--------------------------
 tmp = Val(worlist(0))
 ReDim wor(1 To tmp)
 
 tmp = Val(str(1))
 ReDim num(1 To tmp)
 
'--------------------------
'worを指定
'--------------------------
 For i = 1 To UBound(worlist)
    path = ThisWorkbook.path & "\wor\" & worlist(i) & ".txt"
    box = txt_input(path)
    tmp = Rnd_Num(0, UBound(box))
    wor(i) = box(tmp)
 Next
 
'--------------------------
'numを指定
'--------------------------
 num(1) = Rnd_Num(1, 6)
 num(2) = Rnd_Num(3, 7)
 num(3) = num(1) + num(2)
 
'--------------------------
'問題文を変更
'--------------------------
 For i = LBound(str) + 2 To UBound(str)
    For j = 1 To (worlist(0))
        trgt = "wor" & j
        str(i) = Replace(str(i), trgt, wor(j))
        str(i) = Replace(str(i), trgt, wor(j))
    Next
    For j = 1 To Val(str(1))
        trgt = "num" & j
        str(i) = Replace(str(i), trgt, num(j))
        str(i) = Replace(str(i), trgt, num(j))
    Next
 Next
 
 
'--------------------------
'文章の書き出し
'--------------------------
 For i = LBound(str) + 2 To UBound(str)
    Call Insert_Equation(20, 30 * (i + 1), str(i))
 Next
 
 path = ThisWorkbook.path & "\text\text2_2.txt"
 Call txt_output(str, path)
 
End Sub

パスの設定が少し汚いですが。。
そこは保留。

まとめ

これでまたひとつ枷が外れました。
「計算プリント」系に比べてひながたづくりが大変ですね。