p1atdev commited on
Commit
5bba7a2
·
verified ·
1 Parent(s): d04a7a9

Upload math_problem.py

Browse files
Files changed (1) hide show
  1. math_problem.py +447 -0
math_problem.py ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ from typing import Callable
4
+ from fractions import Fraction
5
+
6
+ # ── ヘルパー関数 ──
7
+
8
+
9
+ def is_terminating(frac: Fraction) -> bool:
10
+ """
11
+ Fraction frac が有限小数(=分母の素因数が2と5のみ)かどうかを判定する
12
+ """
13
+ d = frac.denominator
14
+ while d % 2 == 0:
15
+ d //= 2
16
+ while d % 5 == 0:
17
+ d //= 5
18
+ return d == 1
19
+
20
+
21
+ def precedence(op: str) -> int:
22
+ """演算子の優先順位を返す(+,-:1、*,/:2)"""
23
+ if op in ["+", "-"]:
24
+ return 1
25
+ elif op in ["*", "/"]:
26
+ return 2
27
+ return 0
28
+
29
+
30
+ def fraction_to_decimal_string(f: Fraction, decimal_places: int) -> str:
31
+ """
32
+ Fraction f を decimal_places 桁の小数文字列に変換する。
33
+ ※ f は既に有限小数(循環しない)と仮定。
34
+ """
35
+ return format(float(f), f".{decimal_places}f")
36
+
37
+
38
+ # ── 式ツリーの生成・評価・文字列変換 ──
39
+
40
+
41
+ def generate_expr_tree(n: int, leaf_generator: Callable) -> dict:
42
+ """
43
+ n 個の項(葉)を持つ式ツリーを再帰的に生成する。
44
+ leaf_generator() は葉(数値)を生成する関数。
45
+ 演算子は '+', '-', '*', '/' の中からランダムに選ぶ。
46
+ """
47
+ if n == 1:
48
+ return {"type": "num", "value": leaf_generator()}
49
+ else:
50
+ k = random.randint(1, n - 1)
51
+ left_tree = generate_expr_tree(k, leaf_generator)
52
+ right_tree = generate_expr_tree(n - k, leaf_generator)
53
+ op = random.choice(["+", "-", "*", "/"])
54
+ return {"type": "op", "op": op, "left": left_tree, "right": right_tree}
55
+
56
+
57
+ def evaluate(tree: dict) -> Fraction:
58
+ """
59
+ 式ツリー tree を Fraction を用いて再帰的に評価する。
60
+ ゼロ除算が起こった場合は ZeroDivisionError を発生する。
61
+ """
62
+ if tree["type"] == "num":
63
+ return tree["value"]
64
+ else:
65
+ op = tree["op"]
66
+ left_val = evaluate(tree["left"])
67
+ right_val = evaluate(tree["right"])
68
+ if op == "+":
69
+ return left_val + right_val
70
+ elif op == "-":
71
+ return left_val - right_val
72
+ elif op == "*":
73
+ return left_val * right_val
74
+ elif op == "/":
75
+ if right_val == 0:
76
+ raise ZeroDivisionError
77
+ return left_val / right_val
78
+
79
+
80
+ def tree_to_text(
81
+ tree: dict,
82
+ parent_precedence: int = 0,
83
+ force_paren: bool = False,
84
+ decimal_places: int = None,
85
+ ) -> str:
86
+ """
87
+ 式ツリー tree を普通のテキスト形式の文字列に変換する。
88
+ ・葉("num")の場合、decimal_places が指定されていれば小数形式で表示する。
89
+ ・内部ノードの場合、標準の演算子優先順位に従い必要に応じて括弧を追加するほか、確率的に追加する。
90
+ """
91
+ if tree["type"] == "num":
92
+ if decimal_places is not None:
93
+ return fraction_to_decimal_string(tree["value"], decimal_places)
94
+ else:
95
+ # 整数の場合は Fraction の分母が 1 なら整数として表示
96
+ if tree["value"].denominator == 1:
97
+ return str(tree["value"].numerator)
98
+ else:
99
+ return str(tree["value"])
100
+ else:
101
+ op = tree["op"]
102
+ current_precedence = precedence(op)
103
+ left_str = tree_to_text(
104
+ tree["left"], current_precedence, decimal_places=decimal_places
105
+ )
106
+ right_str = tree_to_text(
107
+ tree["right"], current_precedence, decimal_places=decimal_places
108
+ )
109
+ expr_str = f"{left_str} {op} {right_str}"
110
+ # 親との優先順位関係や、ランダムに括弧を付ける
111
+ if (
112
+ current_precedence < parent_precedence
113
+ or force_paren
114
+ or random.random() < 0.3
115
+ ):
116
+ return f"({expr_str})"
117
+ else:
118
+ return expr_str
119
+
120
+
121
+ def tree_to_tex(
122
+ tree: dict,
123
+ parent_precedence: int = 0,
124
+ force_paren: bool = False,
125
+ decimal_places: int = None,
126
+ ) -> str:
127
+ """
128
+ 式ツリー tree を TeX 形式の文字列に変換する。
129
+ ・掛け算は "\\times"、割り算は分数形式 "\\frac{...}{...}" で表現する。
130
+ ・必要に応じて \left( ... \right) で括弧を追加する。
131
+ """
132
+ if tree["type"] == "num":
133
+ if decimal_places is not None:
134
+ return fraction_to_decimal_string(tree["value"], decimal_places)
135
+ else:
136
+ if tree["value"].denominator == 1:
137
+ return str(tree["value"].numerator)
138
+ else:
139
+ return str(tree["value"])
140
+ else:
141
+ op = tree["op"]
142
+ current_precedence = precedence(op)
143
+ if op == "/":
144
+ # 割り算は常に分数形式で出力
145
+ left_tex = tree_to_tex(tree["left"], 0, decimal_places=decimal_places)
146
+ right_tex = tree_to_tex(tree["right"], 0, decimal_places=decimal_places)
147
+ expr_tex = f"\\frac{{{left_tex}}}{{{right_tex}}}"
148
+ else:
149
+ left_tex = tree_to_tex(
150
+ tree["left"], current_precedence, decimal_places=decimal_places
151
+ )
152
+ right_tex = tree_to_tex(
153
+ tree["right"], current_precedence, decimal_places=decimal_places
154
+ )
155
+ op_tex = op
156
+ if op == "*":
157
+ op_tex = "\\times"
158
+ expr_tex = f"{left_tex} {op_tex} {right_tex}"
159
+ if (
160
+ current_precedence < parent_precedence
161
+ or force_paren
162
+ or random.random() < 0.3
163
+ ):
164
+ return f"\\left({expr_tex}\\right)"
165
+ else:
166
+ return expr_tex
167
+
168
+
169
+ # ── 問題作成関数 ──
170
+
171
+
172
+ # ① 整数の四則演算の問題作成関数
173
+ def create_integer_arithmetic_problem(
174
+ min_val: int = -10, max_val: int = 10, max_terms: int = 5
175
+ ) -> tuple[str, str, str]:
176
+ """
177
+ 指定範囲の整数(負の値も可)と、'+', '-', '*', '/'、括弧を組み合わせた
178
+ 四則演算の計算問題(項数は 2 ~ max_terms のランダムな個数)を作成する。
179
+ ※ただし、計算結果が循環小数(有限小数でない)になったり、ゼロ除算となるものは除外。
180
+ 戻り値は (テキスト形式の式, TeX 形式の式) のタプル。
181
+ """
182
+ while True:
183
+ num_operands = random.randint(2, max_terms)
184
+ tree = generate_expr_tree(
185
+ num_operands, lambda: Fraction(random.randint(min_val, max_val))
186
+ )
187
+ try:
188
+ result = evaluate(tree)
189
+ except ZeroDivisionError:
190
+ continue
191
+ if not is_terminating(result):
192
+ continue
193
+ text_expr = tree_to_text(tree)
194
+ tex_expr = tree_to_tex(tree)
195
+ return text_expr, tex_expr, eval(str(result))
196
+
197
+
198
+ # ② 小数を含む四則演算の問題作成関数
199
+ def generate_decimal_leaf(min_val: int, max_val: int, decimal_places: int) -> Fraction:
200
+ """
201
+ 指定範囲の整数部分に、指定桁数の小数部分を持つ数値を生成する。
202
+ 内部的には Fraction(n, 10**decimal_places) として正確に扱う。
203
+ """
204
+ factor = 10**decimal_places
205
+ n = random.randint(min_val * factor, max_val * factor)
206
+ return Fraction(n, factor)
207
+
208
+
209
+ def create_decimal_arithmetic_problem(
210
+ min_val: int = -10, max_val: int = 10, max_terms: int = 5, decimal_places: int = 1
211
+ ) -> tuple[str, str, str]:
212
+ """
213
+ 小数(有限小数となるように生成)を用いた四則演算の計算問題を作成する関数。
214
+ 戻り値は (テキスト形式の式, TeX 形式の式) のタプル。
215
+ """
216
+ while True:
217
+ num_operands = random.randint(2, max_terms)
218
+ tree = generate_expr_tree(
219
+ num_operands,
220
+ lambda: generate_decimal_leaf(min_val, max_val, decimal_places),
221
+ )
222
+ try:
223
+ result = evaluate(tree)
224
+ except ZeroDivisionError:
225
+ continue
226
+ if not is_terminating(result):
227
+ continue
228
+ text_expr = tree_to_text(tree, decimal_places=decimal_places)
229
+ tex_expr = tree_to_tex(tree, decimal_places=decimal_places)
230
+ return text_expr, tex_expr, eval(str(result))
231
+
232
+
233
+ # ③ 一次方程式の問題作成関数
234
+ def format_coefficient(k: int) -> str:
235
+ """
236
+ 係数 k による x 項の表示を整形する。
237
+ 例えば、1 → "x"、それ以外は "kx" とする。
238
+ """
239
+ if k == 1:
240
+ return "x"
241
+ else:
242
+ return f"{k}x"
243
+
244
+
245
+ def create_linear_equation_problem(
246
+ min_val: int = -10, max_val: int = 10, max_terms: int = 3
247
+ ) -> tuple[str, str, str]:
248
+ """
249
+ 一変数 (x) を含む一次方程式の問題を作成する関数です。
250
+ まず、定数部分 T を(整数の四則演算の問題として)作成し、
251
+ ランダムな非零係数 k と解 x₀ を決定して、
252
+
253
+ T + k*x = T の値 + k*x₀
254
+
255
+ という形の方程式とします。
256
+
257
+ ここで、左辺の表示において x の項の位置を
258
+ "begin"(先頭) / "middle"(定数部分を 2 分割して中央) / "end"(末尾)
259
+ のいずれかにランダムで配置するように変更しています。
260
+
261
+ 戻り値は (テキスト形式の方程式, TeX 形式の方程式, 解 x₀) のタプルです。
262
+ """
263
+
264
+ def create_integer_arithmetic_problem_with_result(
265
+ min_val: int, max_val: int, max_terms: int
266
+ ) -> tuple[str, str, Fraction]:
267
+ while True:
268
+ num_operands = random.randint(2, max_terms)
269
+ tree = generate_expr_tree(
270
+ num_operands, lambda: Fraction(random.randint(min_val, max_val))
271
+ )
272
+ try:
273
+ result = evaluate(tree)
274
+ except ZeroDivisionError:
275
+ continue
276
+ if not is_terminating(result):
277
+ continue
278
+ text_expr = tree_to_text(tree)
279
+ tex_expr = tree_to_tex(tree)
280
+ return text_expr, tex_expr, result
281
+
282
+ # 定数��分 T の式(文字列)とその値 T_value を生成
283
+ text_const, tex_const, T_value = create_integer_arithmetic_problem_with_result(
284
+ min_val, max_val, max_terms
285
+ )
286
+ # 非零の係数 k と解 x₀ をランダムに選ぶ
287
+ possible = [i for i in range(min_val, max_val + 1) if i != 0]
288
+ k = random.choice(possible) if possible else 1
289
+ x0 = random.randint(min_val, max_val)
290
+ # 右辺は T_value + k*x₀
291
+ R_value = T_value + k * x0
292
+
293
+ # x の項の配置位置をランダムに決定
294
+ mode = random.choice(["begin", "middle", "end"])
295
+
296
+ if mode == "begin":
297
+ # x の項を先頭に配置
298
+ if k >= 0:
299
+ lhs_text = f"{format_coefficient(k)} + {text_const}"
300
+ lhs_tex = f"{format_coefficient(k)} + {tex_const}"
301
+ else:
302
+ lhs_text = f"- {format_coefficient(abs(k))} + {text_const}"
303
+ lhs_tex = f"- {format_coefficient(abs(k))} + {tex_const}"
304
+ elif mode == "end":
305
+ # x の項を末尾に配置(従来の形式)
306
+ if k >= 0:
307
+ lhs_text = f"{text_const} + {format_coefficient(k)}"
308
+ lhs_tex = f"{tex_const} + {format_coefficient(k)}"
309
+ else:
310
+ lhs_text = f"{text_const} - {format_coefficient(abs(k))}"
311
+ lhs_tex = f"{tex_const} - {format_coefficient(abs(k))}"
312
+ else: # mode == "middle"
313
+ # 定数 T_value を 2 つの定数 A と B に分割し、その間に x の項を配置
314
+ def format_fraction(frac: Fraction) -> str:
315
+ if frac.denominator == 1:
316
+ return str(frac.numerator)
317
+ else:
318
+ return str(float(frac))
319
+
320
+ A = Fraction(random.randint(min_val, max_val))
321
+ B = T_value - A
322
+ A_text = format_fraction(A)
323
+ B_text = format_fraction(B)
324
+ A_tex = A_text # 数値の場合、TeX 表記も同じ
325
+ B_tex = B_text
326
+ if k >= 0:
327
+ lhs_text = f"{A_text} + {format_coefficient(k)} + {B_text}"
328
+ lhs_tex = f"{A_tex} + {format_coefficient(k)} + {B_tex}"
329
+ else:
330
+ lhs_text = f"{A_text} - {format_coefficient(abs(k))} + {B_text}"
331
+ lhs_tex = f"{A_tex} - {format_coefficient(abs(k))} + {B_tex}"
332
+
333
+ # 右辺の数値表示(整数なら整数、Fraction なら float 表示)
334
+ if R_value.denominator == 1:
335
+ rhs_text = str(R_value.numerator)
336
+ rhs_tex = str(R_value.numerator)
337
+ else:
338
+ rhs_text = str(float(R_value))
339
+ rhs_tex = str(float(R_value))
340
+
341
+ eq_text = lhs_text + " = " + rhs_text
342
+ eq_tex = lhs_tex + " = " + rhs_tex
343
+ return eq_text, eq_tex, eval(str(x0))
344
+
345
+
346
+ def create_two_decimals(
347
+ min_val: float, max_val: float, precision: float
348
+ ) -> tuple[float, float, float]:
349
+ """
350
+ 指定された範囲 [min_val, max_val] 内で、
351
+ 整数部分が共通かつ、小数点以下の桁数(precision)を指定して、
352
+ その精度以下の値をランダムに選んだ2つの数値を生成する関数です。
353
+
354
+ Parameters:
355
+ min_val (float): 生成する数値の最小値
356
+ max_val (float): 生成する数値の最大値
357
+ precision (int): 小数点以下の桁数(0以上の整数)
358
+
359
+ Returns:
360
+ tuple: 生成された2つの数値 (num1, num2)
361
+ """
362
+ if min_val >= max_val:
363
+ raise ValueError("min_valはmax_valより小さくなければなりません。")
364
+ if precision < 0:
365
+ raise ValueError("precisionは0以上の整数である必要があります。")
366
+
367
+ # 指定した精度に応じたスケール(例: precision=3 なら scale=1000)
368
+ scale = int(10**precision)
369
+
370
+ valid_candidates = [] # (整数部分, 許容されるfraction部の整数値リスト) のタプルを格納
371
+
372
+ # 整数部分の候補は、min_valの整数部分からmax_valの整数部分まで
373
+ for k in range(math.floor(min_val), math.floor(max_val) + 1):
374
+ # kを共通の整数部分とする場合の、小数部分がとれる範囲は
375
+ # r = 数値 - k が [r_lower, r_upper) に収まる必要がある
376
+ r_lower = max(0, min_val - k) # 下限
377
+ r_upper = min(1, max_val - k) # 上限(1未満にする)
378
+
379
+ # 小数部分は、指定精度で離散化された値 i/scale (i = 0, 1, ..., scale-1) として扱う
380
+ # i/scale が [r_lower, r_upper) に入る i を抽出します
381
+ allowed_i = [
382
+ i for i in range(scale) if (i / scale) >= r_lower and (i / scale) < r_upper
383
+ ]
384
+
385
+ if allowed_i: # 少なくとも1つの値がとれるなら候補に追加
386
+ valid_candidates.append((k, allowed_i))
387
+
388
+ if not valid_candidates:
389
+ raise ValueError("指定された範囲内で共通の整数部分を持つ数値が生成できません。")
390
+
391
+ # 有効な候補からランダムに1つ選びます
392
+ k, allowed_i = random.choice(valid_candidates)
393
+
394
+ if len(allowed_i) < 2:
395
+ # もし候補が1種類しかなければ、異なる2つの数値を生成できません
396
+ raise ValueError("指定された範囲と精度では、異なる2つの数値を生成できません。")
397
+
398
+ # allowed_i から重複しない2つの値をランダムに選びます
399
+ i1, i2 = random.sample(allowed_i, 2)
400
+
401
+ num1 = k + i1 / scale
402
+ num2 = k + i2 / scale
403
+
404
+ greater = max(num1, num2)
405
+
406
+ return num1, num2, greater
407
+
408
+
409
+ # ── 使用例 ──
410
+
411
+ if __name__ == "__main__":
412
+ print("【整数の四則演算の問題】")
413
+ text_expr, tex_expr, result = create_integer_arithmetic_problem(
414
+ -100,
415
+ 100,
416
+ max_terms=8,
417
+ )
418
+ print("テキスト形式 :", text_expr)
419
+ print("TeX 形式 :", tex_expr)
420
+ print("答え :", result)
421
+
422
+ print("\n【小数を含む四則演算の問題】")
423
+ text_expr, tex_expr, result = create_decimal_arithmetic_problem(
424
+ -100,
425
+ 100,
426
+ max_terms=8,
427
+ decimal_places=1,
428
+ )
429
+ print("テキスト形式 :", text_expr)
430
+ print("TeX 形式 :", tex_expr)
431
+ print("答え :", result)
432
+
433
+ print("\n【一次方程式の問題】")
434
+ eq_text, eq_tex, x0 = create_linear_equation_problem(
435
+ -100,
436
+ 100,
437
+ max_terms=8,
438
+ )
439
+ print("テキスト形式 :", eq_text)
440
+ print("TeX 形式 :", eq_tex)
441
+ print("解 x0 :", x0)
442
+
443
+ print("\n【2つの小数の比較問題】")
444
+ num1, num2, greater = create_two_decimals(-100.0, 100.0, 3)
445
+ print("小数1 :", num1)
446
+ print("小数2 :", num2)
447
+ print("大きい方 :", greater)