| import random |
| import math |
| from typing import Callable |
| from fractions import Fraction |
|
|
| |
|
|
|
|
| def is_terminating(frac: Fraction) -> bool: |
| """ |
| Fraction frac が有限小数(=分母の素因数が2と5のみ)かどうかを判定する |
| """ |
| d = frac.denominator |
| while d % 2 == 0: |
| d //= 2 |
| while d % 5 == 0: |
| d //= 5 |
| return d == 1 |
|
|
|
|
| def precedence(op: str) -> int: |
| """演算子の優先順位を返す(+,-:1、*,/:2)""" |
| if op in ["+", "-"]: |
| return 1 |
| elif op in ["*", "/"]: |
| return 2 |
| return 0 |
|
|
|
|
| def fraction_to_decimal_string(f: Fraction, decimal_places: int) -> str: |
| """ |
| Fraction f を decimal_places 桁の小数文字列に変換する。 |
| ※ f は既に有限小数(循環しない)と仮定。 |
| """ |
| return format(float(f), f".{decimal_places}f") |
|
|
|
|
| |
|
|
|
|
| def generate_expr_tree(n: int, leaf_generator: Callable) -> dict: |
| """ |
| n 個の項(葉)を持つ式ツリーを再帰的に生成する。 |
| leaf_generator() は葉(数値)を生成する関数。 |
| 演算子は '+', '-', '*', '/' の中からランダムに選ぶ。 |
| """ |
| if n == 1: |
| return {"type": "num", "value": leaf_generator()} |
| else: |
| k = random.randint(1, n - 1) |
| left_tree = generate_expr_tree(k, leaf_generator) |
| right_tree = generate_expr_tree(n - k, leaf_generator) |
| op = random.choice(["+", "-", "*", "/"]) |
| return {"type": "op", "op": op, "left": left_tree, "right": right_tree} |
|
|
|
|
| def evaluate(tree: dict) -> Fraction: |
| """ |
| 式ツリー tree を Fraction を用いて再帰的に評価する。 |
| ゼロ除算が起こった場合は ZeroDivisionError を発生する。 |
| """ |
| if tree["type"] == "num": |
| return tree["value"] |
| else: |
| op = tree["op"] |
| left_val = evaluate(tree["left"]) |
| right_val = evaluate(tree["right"]) |
| if op == "+": |
| return left_val + right_val |
| elif op == "-": |
| return left_val - right_val |
| elif op == "*": |
| return left_val * right_val |
| elif op == "/": |
| if right_val == 0: |
| raise ZeroDivisionError |
| return left_val / right_val |
|
|
|
|
| def tree_to_text( |
| tree: dict, |
| parent_precedence: int = 0, |
| force_paren: bool = False, |
| decimal_places: int = None, |
| ) -> str: |
| """ |
| 式ツリー tree を普通のテキスト形式の文字列に変換する。 |
| ・葉("num")の場合、decimal_places が指定されていれば小数形式で表示する。 |
| ・内部ノードの場合、標準の演算子優先順位に従い必要に応じて括弧を追加するほか、確率的に追加する。 |
| """ |
| if tree["type"] == "num": |
| if decimal_places is not None: |
| return fraction_to_decimal_string(tree["value"], decimal_places) |
| else: |
| |
| if tree["value"].denominator == 1: |
| return str(tree["value"].numerator) |
| else: |
| return str(tree["value"]) |
| else: |
| op = tree["op"] |
| current_precedence = precedence(op) |
| left_str = tree_to_text( |
| tree["left"], current_precedence, decimal_places=decimal_places |
| ) |
| right_str = tree_to_text( |
| tree["right"], current_precedence, decimal_places=decimal_places |
| ) |
| expr_str = f"{left_str} {op} {right_str}" |
| |
| if ( |
| current_precedence < parent_precedence |
| or force_paren |
| or random.random() < 0.3 |
| ): |
| return f"({expr_str})" |
| else: |
| return expr_str |
|
|
|
|
| def tree_to_tex( |
| tree: dict, |
| parent_precedence: int = 0, |
| force_paren: bool = False, |
| decimal_places: int = None, |
| ) -> str: |
| """ |
| 式ツリー tree を TeX 形式の文字列に変換する。 |
| ・掛け算は "\\times"、割り算は分数形式 "\\frac{...}{...}" で表現する。 |
| ・必要に応じて \left( ... \right) で括弧を追加する。 |
| """ |
| if tree["type"] == "num": |
| if decimal_places is not None: |
| return fraction_to_decimal_string(tree["value"], decimal_places) |
| else: |
| if tree["value"].denominator == 1: |
| return str(tree["value"].numerator) |
| else: |
| return str(tree["value"]) |
| else: |
| op = tree["op"] |
| current_precedence = precedence(op) |
| if op == "/": |
| |
| left_tex = tree_to_tex(tree["left"], 0, decimal_places=decimal_places) |
| right_tex = tree_to_tex(tree["right"], 0, decimal_places=decimal_places) |
| expr_tex = f"\\frac{{{left_tex}}}{{{right_tex}}}" |
| else: |
| left_tex = tree_to_tex( |
| tree["left"], current_precedence, decimal_places=decimal_places |
| ) |
| right_tex = tree_to_tex( |
| tree["right"], current_precedence, decimal_places=decimal_places |
| ) |
| op_tex = op |
| if op == "*": |
| op_tex = "\\times" |
| expr_tex = f"{left_tex} {op_tex} {right_tex}" |
| if ( |
| current_precedence < parent_precedence |
| or force_paren |
| or random.random() < 0.3 |
| ): |
| return f"\\left({expr_tex}\\right)" |
| else: |
| return expr_tex |
|
|
|
|
| |
|
|
|
|
| |
| def create_integer_arithmetic_problem( |
| min_val: int = -10, max_val: int = 10, max_terms: int = 5 |
| ) -> tuple[str, str, str]: |
| """ |
| 指定範囲の整数(負の値も可)と、'+', '-', '*', '/'、括弧を組み合わせた |
| 四則演算の計算問題(項数は 2 ~ max_terms のランダムな個数)を作成する。 |
| ※ただし、計算結果が循環小数(有限小数でない)になったり、ゼロ除算となるものは除外。 |
| 戻り値は (テキスト形式の式, TeX 形式の式) のタプル。 |
| """ |
| while True: |
| num_operands = random.randint(2, max_terms) |
| tree = generate_expr_tree( |
| num_operands, lambda: Fraction(random.randint(min_val, max_val)) |
| ) |
| try: |
| result = evaluate(tree) |
| except ZeroDivisionError: |
| continue |
| if not is_terminating(result): |
| continue |
| text_expr = tree_to_text(tree) |
| tex_expr = tree_to_tex(tree) |
| return text_expr, tex_expr, eval(str(result)) |
|
|
|
|
| |
| def generate_decimal_leaf(min_val: int, max_val: int, decimal_places: int) -> Fraction: |
| """ |
| 指定範囲の整数部分に、指定桁数の小数部分を持つ数値を生成する。 |
| 内部的には Fraction(n, 10**decimal_places) として正確に扱う。 |
| """ |
| factor = 10**decimal_places |
| n = random.randint(min_val * factor, max_val * factor) |
| return Fraction(n, factor) |
|
|
|
|
| def create_decimal_arithmetic_problem( |
| min_val: int = -10, max_val: int = 10, max_terms: int = 5, decimal_places: int = 1 |
| ) -> tuple[str, str, str]: |
| """ |
| 小数(有限小数となるように生成)を用いた四則演算の計算問題を作成する関数。 |
| 戻り値は (テキスト形式の式, TeX 形式の式) のタプル。 |
| """ |
| while True: |
| num_operands = random.randint(2, max_terms) |
| tree = generate_expr_tree( |
| num_operands, |
| lambda: generate_decimal_leaf(min_val, max_val, decimal_places), |
| ) |
| try: |
| result = evaluate(tree) |
| except ZeroDivisionError: |
| continue |
| if not is_terminating(result): |
| continue |
| text_expr = tree_to_text(tree, decimal_places=decimal_places) |
| tex_expr = tree_to_tex(tree, decimal_places=decimal_places) |
| return text_expr, tex_expr, eval(str(result)) |
|
|
|
|
| |
| def format_coefficient(k: int) -> str: |
| """ |
| 係数 k による x 項の表示を整形する。 |
| 例えば、1 → "x"、それ以外は "kx" とする。 |
| """ |
| if k == 1: |
| return "x" |
| else: |
| return f"{k}x" |
|
|
|
|
| def create_linear_equation_problem( |
| min_val: int = -10, max_val: int = 10, max_terms: int = 3 |
| ) -> tuple[str, str, str]: |
| """ |
| 一変数 (x) を含む一次方程式の問題を作成する関数です。 |
| まず、定数部分 T を(整数の四則演算の問題として)作成し、 |
| ランダムな非零係数 k と解 x₀ を決定して、 |
| |
| T + k*x = T の値 + k*x₀ |
| |
| という形の方程式とします。 |
| |
| ここで、左辺の表示において x の項の位置を |
| "begin"(先頭) / "middle"(定数部分を 2 分割して中央) / "end"(末尾) |
| のいずれかにランダムで配置するように変更しています。 |
| |
| 戻り値は (テキスト形式の方程式, TeX 形式の方程式, 解 x₀) のタプルです。 |
| """ |
|
|
| def create_integer_arithmetic_problem_with_result( |
| min_val: int, max_val: int, max_terms: int |
| ) -> tuple[str, str, Fraction]: |
| while True: |
| num_operands = random.randint(2, max_terms) |
| tree = generate_expr_tree( |
| num_operands, lambda: Fraction(random.randint(min_val, max_val)) |
| ) |
| try: |
| result = evaluate(tree) |
| except ZeroDivisionError: |
| continue |
| if not is_terminating(result): |
| continue |
| text_expr = tree_to_text(tree) |
| tex_expr = tree_to_tex(tree) |
| return text_expr, tex_expr, result |
|
|
| |
| text_const, tex_const, T_value = create_integer_arithmetic_problem_with_result( |
| min_val, max_val, max_terms |
| ) |
| |
| possible = [i for i in range(min_val, max_val + 1) if i != 0] |
| k = random.choice(possible) if possible else 1 |
| x0 = random.randint(min_val, max_val) |
| |
| R_value = T_value + k * x0 |
|
|
| |
| mode = random.choice(["begin", "middle", "end"]) |
|
|
| if mode == "begin": |
| |
| if k >= 0: |
| lhs_text = f"{format_coefficient(k)} + {text_const}" |
| lhs_tex = f"{format_coefficient(k)} + {tex_const}" |
| else: |
| lhs_text = f"- {format_coefficient(abs(k))} + {text_const}" |
| lhs_tex = f"- {format_coefficient(abs(k))} + {tex_const}" |
| elif mode == "end": |
| |
| if k >= 0: |
| lhs_text = f"{text_const} + {format_coefficient(k)}" |
| lhs_tex = f"{tex_const} + {format_coefficient(k)}" |
| else: |
| lhs_text = f"{text_const} - {format_coefficient(abs(k))}" |
| lhs_tex = f"{tex_const} - {format_coefficient(abs(k))}" |
| else: |
| |
| def format_fraction(frac: Fraction) -> str: |
| if frac.denominator == 1: |
| return str(frac.numerator) |
| else: |
| return str(float(frac)) |
|
|
| A = Fraction(random.randint(min_val, max_val)) |
| B = T_value - A |
| A_text = format_fraction(A) |
| B_text = format_fraction(B) |
| A_tex = A_text |
| B_tex = B_text |
| if k >= 0: |
| lhs_text = f"{A_text} + {format_coefficient(k)} + {B_text}" |
| lhs_tex = f"{A_tex} + {format_coefficient(k)} + {B_tex}" |
| else: |
| lhs_text = f"{A_text} - {format_coefficient(abs(k))} + {B_text}" |
| lhs_tex = f"{A_tex} - {format_coefficient(abs(k))} + {B_tex}" |
|
|
| |
| if R_value.denominator == 1: |
| rhs_text = str(R_value.numerator) |
| rhs_tex = str(R_value.numerator) |
| else: |
| rhs_text = str(float(R_value)) |
| rhs_tex = str(float(R_value)) |
|
|
| eq_text = lhs_text + " = " + rhs_text |
| eq_tex = lhs_tex + " = " + rhs_tex |
| return eq_text, eq_tex, eval(str(x0)) |
|
|
|
|
| def create_two_decimals( |
| min_val: float, max_val: float, precision: float |
| ) -> tuple[float, float, float]: |
| """ |
| 指定された範囲 [min_val, max_val] 内で、 |
| 整数部分が共通かつ、小数点以下の桁数(precision)を指定して、 |
| その精度以下の値をランダムに選んだ2つの数値を生成する関数です。 |
| |
| Parameters: |
| min_val (float): 生成する数値の最小値 |
| max_val (float): 生成する数値の最大値 |
| precision (int): 小数点以下の桁数(0以上の整数) |
| |
| Returns: |
| tuple: 生成された2つの数値 (num1, num2) |
| """ |
| if min_val >= max_val: |
| raise ValueError("min_valはmax_valより小さくなければなりません。") |
| if precision < 0: |
| raise ValueError("precisionは0以上の整数である必要があります。") |
|
|
| |
| scale = int(10**precision) |
|
|
| valid_candidates = [] |
|
|
| |
| for k in range(math.floor(min_val), math.floor(max_val) + 1): |
| |
| |
| r_lower = max(0, min_val - k) |
| r_upper = min(1, max_val - k) |
|
|
| |
| |
| allowed_i = [ |
| i for i in range(scale) if (i / scale) >= r_lower and (i / scale) < r_upper |
| ] |
|
|
| if allowed_i: |
| valid_candidates.append((k, allowed_i)) |
|
|
| if not valid_candidates: |
| raise ValueError("指定された範囲内で共通の整数部分を持つ数値が生成できません。") |
|
|
| |
| k, allowed_i = random.choice(valid_candidates) |
|
|
| if len(allowed_i) < 2: |
| |
| raise ValueError("指定された範囲と精度では、異なる2つの数値を生成できません。") |
|
|
| |
| i1, i2 = random.sample(allowed_i, 2) |
|
|
| num1 = k + i1 / scale |
| num2 = k + i2 / scale |
|
|
| greater = max(num1, num2) |
|
|
| return num1, num2, greater |
|
|
|
|
| |
|
|
| if __name__ == "__main__": |
| print("【整数の四則演算の問題】") |
| text_expr, tex_expr, result = create_integer_arithmetic_problem( |
| -100, |
| 100, |
| max_terms=8, |
| ) |
| print("テキスト形式 :", text_expr) |
| print("TeX 形式 :", tex_expr) |
| print("答え :", result) |
|
|
| print("\n【小数を含む四則演算の問題】") |
| text_expr, tex_expr, result = create_decimal_arithmetic_problem( |
| -100, |
| 100, |
| max_terms=8, |
| decimal_places=1, |
| ) |
| print("テキスト形式 :", text_expr) |
| print("TeX 形式 :", tex_expr) |
| print("答え :", result) |
|
|
| print("\n【一次方程式の問題】") |
| eq_text, eq_tex, x0 = create_linear_equation_problem( |
| -100, |
| 100, |
| max_terms=8, |
| ) |
| print("テキスト形式 :", eq_text) |
| print("TeX 形式 :", eq_tex) |
| print("解 x0 :", x0) |
|
|
| print("\n【2つの小数の比較問題】") |
| num1, num2, greater = create_two_decimals(-100.0, 100.0, 3) |
| print("小数1 :", num1) |
| print("小数2 :", num2) |
| print("大きい方 :", greater) |
|
|