文章問題を自動生成する その3_1 ひながた (worとnumの個数を考慮)
はじめに
前回現れた多くの課題を解決していきます。
「その1」と「その2」で扱った二つの文章に、できるだけ対応できる関数を作ることが「その3」の目的です。
なので抽象的な話が多くなります。
均したい部分
前回コードを変更した部分を再びあげます。
「wor1」を書き換える候補を手書きで加えた。
「wor2」を新たに加えた。
「wor」配列の個数を変更した。
「num1」「num2」の値の範囲を変更した。
「num3」の求め方を変えた。
「wor」を書き換える構文を変更した。
今回は、「wor」と「num」の個数を考慮してくれる仕様にします。
自動でやってみる
扱う文章
「その1」で使ったものを一部変更します。
1 3 '-------------------------- '問題文 '-------------------------- wor1が1個num1円で売られています。 wor1をnum2つ買う時の値段はいくらでしょう。
'-------------------------- '解き方 '-------------------------- num2×num1=num3 よってnum3円
1行目にworの数を、2行目にnumの数を打ち込みました。
コード
Sub 文章題生成_3_1() '-------------------------- '文章第の一部を書き換える '-------------------------- '-------------------------- '変数の定義 '-------------------------- Dim str() As String '文章 Dim path As String 'メモ帳のアドレス Dim tmp As Integer '一時的な保管変数 Dim box As Variant 'ワードの候補 Dim wor() As String 'ワード入れ替え用の変数 Dim num() As Integer '数値入れ替え用の変数 Dim trgt As String '数値入れ替え用の変数 Dim i As Integer '繰り返しの制御変数 Dim j As Integer '繰り返しの制御変数 '-------------------------- '文章の読み込み '-------------------------- path = ThisWorkbook.path & "\text\text1_1.txt" str = txt_input(path) '-------------------------- '書き換えの用意 '-------------------------- '-------------------------- 'worとnumの配列数を取得 '-------------------------- tmp = Val(str(0)) ReDim wor(1 To tmp) tmp = Val(str(1)) ReDim num(1 To tmp) box = Array("りんご", "みかん", "なし", "もも", "かき") tmp = Rnd_Num(0, UBound(box)) wor(1) = box(tmp) num(1) = Rnd_Num(1, 5) * 10 + 100 num(2) = Rnd_Num(3, 7) num(3) = num(1) * num(2) '-------------------------- '問題文を変更 '-------------------------- For i = LBound(str) + 2 To UBound(str) For j = 1 To Val(str(0)) trgt = "wor" & 1 str(i) = Replace(str(i), trgt, wor(1)) str(i) = Replace(str(i), trgt, wor(1)) 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\text1_2.txt" Call txt_output(str, path) End Sub
まとめ
地味ですが、恩恵は大きいです。
これで文章が変わっても関数の構造自体は変更せずに済みます。
こんな調子で少しずつ課題をつぶしていきます。
文章問題を自動生成する その2 ぜんぶでいくつ? (変更点をみつける)
はじめに
今回は新しい文章を扱います。
前回作成した関数をそのまま流用できるのだろうか……
手動でやってみる
問題
だいご君はあめを3つもっています。
いま、さらに5つのあめをもらいました。
もっているあめはぜんぶでいくつでしょうか。
解き方
3+5=8
よって8つ
書き換えの準備
まずは文章にマークを付けないといけません。
ワードは「あめ」
数値は「3」と「5」と「8」
をマーキングします。
この作業は今のところ手動です。
目で見て、手でひとつひとつ書き換えています。
'--------------------------
'問題文
'--------------------------
だいご君はwor1をnum1つもっています。
いま、さらにnum2つのwor1をもらいました。
もっているwor1はぜんぶでいくつでしょうか。
'--------------------------
'解き方
'--------------------------
num1+num2=num3
よってnum3つ
自動でやってみるPart1
前回作成した「文章題生成_3」に投げてみます。
メモ帳のアドレスなどは手動で書き換えています。
生成した文章Part1
'--------------------------
'問題文
'--------------------------
だいご君はももを110つもっています。
いま、さらに5つのももをもらいました。
もっているももはぜんぶでいくつでしょうか。
'--------------------------
'解き方
'--------------------------
110+5=550
よって550つ
考察Part1
んー。すごいおかしいですね。
おかしな点をリストアップ。
ももを110個持っているのは多すぎる。
110つではなく110個と表記してほしい。
計算が間違っている。
致命的な欠点は計算が間違っていること。
これは「num3」の求め方が「num1×num2」となっているからです。
この部分を「num1+num2」とすれば解決はするのですが。
今回はそれで。
「num1」と「num2」は乱数の範囲を書き換えます。
ここも妥協。
あ、あとせっかくなので「だいご君」も書き換えられるようにしましょう。
自動でやってみるPart2
扱う文章題
'--------------------------
'問題文
'--------------------------
wor1はwor2をnum1つもっています。
いま、さらにnum2つのwor2をもらいました。
もっているwor2はぜんぶでいくつでしょうか。
'--------------------------
'解き方
'--------------------------
num1+num2=num3
よってnum3つ
コードPart2
Sub 文章題生成_4() '-------------------------- '文章第の一部を書き換える '--------------------------
'-------------------------- '変数の定義 '-------------------------- Dim str() As String '文章 Dim path As String 'メモ帳のアドレス Dim box As Variant 'ワードの候補 Dim wor(1 To 2) As String 'ワード入れ替え用の変数 Dim num(1 To 3) As Integer '数値入れ替え用の変数 Dim trgt As String '数値入れ替え用の変数 Dim i As Integer '繰り返しの制御変数 Dim j As Integer '繰り返しの制御変数
'-------------------------- '文章の読み込み '-------------------------- path = ThisWorkbook.path & "\text\text2_1.txt" str = txt_input(path)
'-------------------------- '書き換えの用意 '-------------------------- box = Array("だいご君", "ひかる君", "むつこちゃん", "うらべ君") tmp = Rnd_Num(0, UBound(box)) wor(1) = box(tmp) box = Array("あめ", "りんご", "みかん", "なし", "もも", "かき") tmp = Rnd_Num(0, UBound(box)) wor(2) = box(tmp)
num(1) = Rnd_Num(1, 6) num(2) = Rnd_Num(3, 7) num(3) = num(1) + num(2)
'-------------------------- '問題文を変更 '-------------------------- For i = LBound(str) To UBound(str) For j = 1 To 2 trgt = "wor" & j str(i) = Replace(str(i), trgt, wor(j)) str(i) = Replace(str(i), trgt, wor(j)) Next For j = 1 To 3 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) 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
生成した文章Part2
'--------------------------
'問題文
'--------------------------
むつこちゃんはなしを4つもっています。
いま、さらに4つのなしをもらいました。
もっているなしはぜんぶでいくつでしょうか。
'--------------------------
'解き方
'--------------------------
4+4=8
よって8つ
考察Part2
新たに手を加えた部分をリストアップします。
「wor1」を書き換える候補を手書きで加えた。
「wor2」を新たに加えた。
「wor」配列の個数を変更した。
「num1」「num2」の値の範囲を変更した。
「num3」の求め方を変えた。
「wor」を書き換える構文を変更した。
感覚的には相当書き換えたな、という感じです。
まとめ
「文章題生成3」はぜんぜん汎用性のない関数であることが分かりました。
当然ながら「文章題生成4」も同様です。
文章題を変えるたびにコードのあちこちを変更するのは疲れるので、できるだけ手間を減らすことが次の課題です。
つぶせるものからひとつずつつぶしていきましょう。
文章問題を自動生成する その1_3 りんごの値段は?(文章の入出力)
はじめに
いままではコード内やイミディエイトウィンドウで文章を管理していました。
それだと「文章の切り替え」に弱いので、今回は文章の管理について書きます。
文章の管理
コード内でないところで文章を管理する。
ぱっと思いつくのはExcelのシートですが、今回はメモ帳で管理することにします。
シートで管理しようとするとごちゃごちゃになりそうな気がするので。
ここはおそらく趣味の問題だと思います。
コード
文章題生成_3
まだ「ひながた」がないので、最初に書いた「文章題生成_1」をちょっとずつ更新しています。
Sub 文章題生成_3() '-------------------------- '文章第の一部を書き換える '-------------------------- '-------------------------- '変数の定義 '-------------------------- Dim str() As String '文章 Dim path As String 'メモ帳のアドレス Dim box As Variant 'ワードの候補 Dim wor(1) As String 'ワード入れ替え用の変数 Dim num(1 To 3) As Integer '数値入れ替え用の変数 Dim trgt As String '数値入れ替え用の変数 Dim i As Integer '繰り返しの制御変数 Dim j As Integer '繰り返しの制御変数 '-------------------------- '文章の読み込み '-------------------------- path = ThisWorkbook.path & "\text\text1.txt" str = txt_input(path) '-------------------------- '書き換えの用意 '-------------------------- box = Array("りんご", "みかん", "なし", "もも", "かき") tmp = Rnd_Num(0, UBound(box)) wor(1) = box(tmp) num(1) = Rnd_Num(1, 5) * 10 + 100 num(2) = Rnd_Num(3, 7) num(3) = num(1) * num(2) '-------------------------- '問題文を変更 '-------------------------- For i = LBound(str) To UBound(str) trgt = "wor" & 1 str(i) = Replace(str(i), trgt, wor(1)) str(i) = Replace(str(i), trgt, wor(1)) For j = 1 To 3 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) To UBound(str) Call Insert_Equation(20, 30 * (i + 1), str(i)) Next path = ThisWorkbook.path & "\text\text2.txt" Call txt_output(str, path) End Sub
変更点をつらつらと。
文章の入力をメモ帳からするようにした。
文章の「ワード」と「数値」を変更する仕様にした。
変更した文章をシート上に出力するようにした。
変更した文章をメモ帳に保管するようにした。
生成した文章
'--------------------------
'問題文
'--------------------------
りんごが1個140円で売られています。
りんごを7つ買う時の値段はいくらでしょう。
'--------------------------
'解き方
'--------------------------
7×140=980
よって980円
まとめ
これで文章の管理が楽になりました。
次回は扱う文章を変えてみます。
知らず知らずのうちに「この文章専用のコード」になってしまっているので
新しい問題を扱うことでその枷を外しましょう。
文章問題を自動生成する その1_2 りんごの値段は? (数値を変更)
はじめに
前回は文章題の「解き方にかかわらない部分」を書き換えました。
今回は「解き方にかかわる部分」を書き換えてみます。
ひながたの問題
書き換えるもとになる文章です。
問題
りんごが1個100円で売られています。
りんごを3つ買う時の値段はいくらでしょう。
解き方
3×100=300
よって300円。
自動でやってみる
今回は「問題文」と「解き方」の両方を書き換える必要があります。
さらに「解き方」では数値の再計算をする必要があります。
Sub 文章題生成_2() '-------------------------- '文章第の一部を書き換える '-------------------------- '-------------------------- '変数の定義 '-------------------------- Dim strA(1 To 2) As String '問題文 Dim strB(1 To 2) As String '解き方 Dim num(1 To 3) As Integer '入れ替え用の変数 Dim trgt As String '入れ替え用の変数 Dim i As Integer '繰り返しの制御変数 Dim j As Integer '繰り返しの制御変数 '-------------------------- '問題文のひながた '-------------------------- strA(1) = "りんごが1個num1円で売られています。" strA(2) = "りんごをnum2つ買う時の値段はいくらでしょう。 " '-------------------------- '解き方のひながた '-------------------------- strB(1) = "num1×num2=num3" strB(2) = "よってnum3円" '-------------------------- '問題文を変更 '-------------------------- num(1) = Rnd_Num(1, 5) * 10 + 100 num(2) = Rnd_Num(3, 7) num(3) = num(1) * num(2) For i = 1 To 3 trgt = "num" & i For j = 1 To 2 strA(j) = Replace(strA(j), trgt, num(i)) strB(j) = Replace(strB(j), trgt, num(i)) Next Next '-------------------------- 'Debug '-------------------------- For i = 1 To 2 Debug.Print strA(i) Next For i = 1 To 2 Debug.Print strB(i) Next End Sub
ひながたとなる文章の一部を変更しました。
書き換えたい部分をあらかじめマークしておくと、簡単に処理できるみたいです。
生成結果
せっかくなので生成された文章を載せておきます。
りんごが1個130円で売られています。
りんごを4つ買う時の値段はいくらでしょう。
130×4=520
よって520円
まとめ
簡単な文章題を一問だけ手動で作れば、それに似た問題を自動生成できるようになりました。
意外と簡単でしたね。
次回はひながたの文章と書き換えた文章の管理について書きます。
文章問題を自動生成する その1_1 りんごの値段は? (りんごを変更)
はじめに
今回からしばらく文章題を扱います。
ノリとしては「計算プリントを自動生成する」と同じなのですが
文章題と計算プリントってちょっと違うよなーと思ってタイトルを変えました。
「計算プリント系」で書いたことを前提として進めていきます。
「その1」は慣らしです。
とりあえず取り組んでみて、どんなことを考えればいいかをとらえます。
手動でやってみる
とりあえず手動でやってみましょう。
自分で問題を一問生成します。
問題
りんごが1個100円で売られています。
りんごを3つ買う時の値段はいくらでしょう。
解き方
3×100=300
よって300円。
文章題の生成について
とりあえず1問作ってみて思ったこと。
「問題文」と「解き方」の2つをつくる必要がある。
「問題文」は文字を乱数で並べたものではない。
「解き方」は問題文の意味を読み取って作る。
何もない状態から上のような問題を作成して、さらにその解き方を作成するのは難しそう。
なので、手動で作ったものの一部を書き換えることで生成をします。
自動でやってみる
今回は、りんごを別の果物に書き換えるコードにします。
Sub 文章題生成_1() '-------------------------- '文章第の一部を書き換える '-------------------------- '-------------------------- '変数の定義 '-------------------------- Dim strA(1 To 2) As String '問題文 Dim strB(1 To 2) As String '解き方 Dim box As Variant 'くだもの Dim tmp As Integer 'くだものを選ぶ変数 Dim trgt As String '書き換え変数 Dim i As Integer '繰り返しの制御変数 '-------------------------- '問題文のひながた '-------------------------- strA(1) = "りんごが1個100円で売られています。" strA(2) = "りんごを3つ買う時の値段はいくらでしょう。 " '-------------------------- '解き方のひながた '-------------------------- strB(1) = "3×100=300" strB(2) = "よって300円" '-------------------------- '問題文を変更 '-------------------------- box = Array("りんご", "みかん", "なし", "もも", "かき") tmp = Rnd_Num(0, UBound(box)) trgt = box(tmp) For i = 1 To 2 strA(i) = Replace(strA(i), "りんご", trgt) Next '-------------------------- 'Debug '-------------------------- For i = 1 To 2 Debug.Print strA(i) Next For i = 1 To 2 Debug.Print strB(i) Next End Sub
とりあえずつらつらと。
一回動かしてみたら、「りんご」から「もも」に変わっていました笑
まとめ
文章の「解き方にかかわらない部分」の書き換えは思った以上に簡単でした。
次回はりんごの値段をと買う個数を書き換えてみましょう。
計算プリントを自動生成する その14_4 関数の形を整える
はじめに
これらの記事は一週間分くらい書き溜めてからブログに予約投稿しているのですが。
前回の記事とコードを書いてから2,3日たってだいぶ落ち着いてきたので。
改めて修正をしたいと思います。
コードの修正
Fractionクラス
Public va As Integer '分子 Public vb As Integer '分母 Public S As String '文字列 Private Sub Class_Initialize() va = 0 vb = 1 End Sub Function Conversion(str) '------------------- '文字式から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 vb = Val(stock) Call Fit End Function Function Fit() '--------------------- '約分してStrを作成 '--------------------- If va = 0 Then S = "0" End If L = Application.WorksheetFunction.Gcd(Abs(va), Abs(vb)) va = va / L vb = vb / L If vb = 1 Then S = va Else S = va & "/" & vb End If End Function
やはり多少奇妙な感じがするのですが、こちらのほうが使い勝手がいいのでそうします。
Int_Str_Make
この関数には「計算式を作成するための骨格」という役割を持たせることにしました。
以降のサブ関数たちの戻り値を使って式を作成する関数です。
まだ機能をサブ関数に与え切れていないのですが、前回よりは見やすいと思います。
Function Int_Str_Make(NN, version) As String '------------------ '変数の定義 '------------------ Dim ope() As String '演算子 Dim num() As Fraction '数値 Dim i As Integer '繰り返しの制御変数 Dim box As Variant '演算子決定のアシスト変数 Dim tmp As Integer '演算子決定のアシスト変数 Dim str As String '計算式 Dim ans As Fraction '数式の解を管理 Dim cnt As Integer 'loop管理の変数 Dim flag As Boolean 'loop管理の変数 '------------------ 'opeとnumの配列数を指定 '------------------ ReDim ope(1 To NN) ReDim num(1 To NN) '------------------ 'boxの指定 '------------------ box = Array("+", "-", "×", "÷") '------------------ '数式を仮決め '------------------ ReSelect: cnt = 0 '------------------ 'opeの値を指定 '------------------ For i = 1 To NN - 1 tmp = Rnd_Num(0, 3) ope(i) = box(tmp) Next '------------------ 'numの値を仮決め '------------------ For i = 1 To NN Set num(i) = New Fraction Set num(i) = Int_Num_Select() Next '------------------- 'numの値を本決め '------------------- Do '------------------ 'loopの初期設定 '------------------ flag = False str = "" '------------------ '停滞している場合 '------------------ If cnt > 20 Then GoTo ReSelect End If '------------------ 'ansを作成 '------------------ For i = 1 To NN num(i).Fit str = str & num(i).S & ope(i) Next Set ans = Int_Str_Analyze(str) '------------------ '繰り返すかの判定 '------------------ flag = Int_ReDo_Jdg(num, ope, ans) cnt = cnt + 1 Loop While flag = True '------------------ 'strを再構成 '------------------ str = str & "=" & ans.S Int_Str_Make = str End Function
数値の指定、答えの解析、答えの判定、をサブ関数に行わせています。
前回はサブ関数への仕分けが下手だったので汚くなってしまったのかなと思います。
仕分けは機能を俯瞰して見られていれば簡単なのですが、関数を書くことにいっぱいいっぱいになっているとなかなかできないですね。
Int_Num_Select
後のことも考えて、型をFractionにしています。
Function Int_Num_Select() As Fraction Dim num As Fraction '戻り値 Set num = New Fraction num.va = Rnd_Num(1, 10) num.Fit Set Int_Num_Select = num End Function
Int_Str_Analyze
前回よりも理にかなったコードになったと思います。
ただそのためにサブ関数が増えました。
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 '計算のアシスト変数 '---------------------- '変数の初期化 '---------------------- 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).Conversion (stock) 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).Conversion (stock) stock = "" End If Rtrn: '---------------------- '条件を満たしている場合は終了 '---------------------- If ope(UBound(ope)) = "END" Then num(0).Fit Set Int_Str_Analyze = num(0) Exit Function End If '---------------------- '計算を行う '---------------------- trgt2 = "" For i = 0 To UBound(ope) Select Case ope(i) Case "×" num(i).va = num(i).va * num(i + 1).va num(i).vb = num(i).vb * num(i + 1).vb Call V_Shift(ope, i) Call O_Shift(num, i + 1) GoTo Rtrn Case "÷" num(i).va = num(i).va * num(i + 1).vb num(i).vb = num(i).vb * num(i + 1).va Call V_Shift(ope, i) Call O_Shift(num, i + 1) GoTo Rtrn End Select Next For i = 0 To UBound(ope) Select Case ope(i) Case "+" num(i).va = num(i).va * num(i + 1).vb + num(i).vb * num(i + 1).va num(i).vb = num(i).vb * num(i + 1).vb Call V_Shift(ope, i) Call O_Shift(num, i + 1) GoTo Rtrn Case "-" num(i).va = num(i).va * num(i + 1).vb - num(i).vb * num(i + 1).va num(i).vb = num(i).vb * num(i + 1).vb Call V_Shift(ope, i) Call O_Shift(num, i + 1) GoTo Rtrn End Select Next '---------------- '予期せぬ処理の場合はSTOP '---------------- Stop End Function
Shift系
これらは専用ツールみたいな感じです。
まずはV_Shift
Function V_Shift(trgt, num) For i = num To UBound(trgt) - 1 trgt(i) = trgt(i + 1) Next If UBound(trgt) > 0 Then ReDim Preserve trgt(LBound(trgt) To UBound(trgt) - 1) Else trgt(0) = "END" End If End Function
次にO_Shift
Function O_Shift(trgt, num) For i = num To UBound(trgt) - 1 Set trgt(i) = trgt(i + 1) Next ReDim Preserve trgt(LBound(trgt) To UBound(trgt) - 1) End Function
Int_ReDo_Jdg
前回はこの機能を関数に仕分けていなかったのが良くなかった。
Function Int_ReDo_Jdg(num, ope, ans) As Boolean '------------------------- 'ansを考慮して '必要な場合はnumの値を変更 '-------------------------- '-------------------------- '変数の定義 '-------------------------- Dim i As Integer '-------------------------- 'ansが負の数の場合 '-------------------------- If ans.va < 0 Then num(1).va = num(1).va + Abs(ans.va) \ 4 For i = 1 To NN - 1 Select Case ope(i) Case Is = "+" num(i + 1).va = num(i + 1).va + 1 Case Is = "-" num(i + 1).va = Application.WorksheetFunction.Max(2, num(i + 1).va - 1) End Select Next Int_ReDo_Jdg = True Exit Function End If '-------------------------- 'ansが分数の場合 '-------------------------- If ans.vb > 1 Then num(1).va = num(1).va * ans.vb For i = 1 To NN - 1 If ope(i) = "+" Or ope(i) = "-" Then num(i + 1).va = num(i + 1).va * ans.vb End If Next Int_ReDo_Jdg = True Exit Function End If '-------------------------- 'ansが100より大きい場合 '-------------------------- If ans.va > 100 Then For i = 1 To NN - 1 If ope(i) <> "÷" Then num(i).va = Application.WorksheetFunction.Max(2, num(i).va \ 2) End If Next num(i).va = Application.WorksheetFunction.Max(2, num(i).va - ans.va \ 3) Int_ReDo_Jdg = True Exit Function End If End Function
この条件を書き換えることで分数の計算や負の数の計算にも対応できます。
ただまだまだ考える余地はありますが。
まとめ
言うほどきれいには整えられませんでしたが、とりあえず自分の頭はすっきりしました。
これで一区切り。
つぎはどこに石を打とうか、というところですね。
この関数の強化か
文章題への進出か
文字式への進出か
図形への進出か
どれもいつかはやるので
興味のあるものからやりましょう。
次回は文章題へ進出します。
計算プリントを自動生成する その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
まとめ
余裕がなかったのでかなりわかりにくい関数たちになってしまいました。
次回にもうちょっと形を整えます。