llm-jp-3-3.7b-instruct2-R27 / math_problem.py
p1atdev's picture
Upload math_problem.py
5bba7a2 verified
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:
# 整数の場合は Fraction の分母が 1 なら整数として表示
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
# 定数部分 T の式(文字列)とその値 T_value を生成
text_const, tex_const, T_value = create_integer_arithmetic_problem_with_result(
min_val, max_val, max_terms
)
# 非零の係数 k と解 x₀ をランダムに選ぶ
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)
# 右辺は T_value + k*x₀
R_value = T_value + k * x0
# x の項の配置位置をランダムに決定
mode = random.choice(["begin", "middle", "end"])
if mode == "begin":
# x の項を先頭に配置
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":
# x の項を末尾に配置(従来の形式)
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: # mode == "middle"
# 定数 T_value を 2 つの定数 A と B に分割し、その間に x の項を配置
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 # 数値の場合、TeX 表記も同じ
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}"
# 右辺の数値表示(整数なら整数、Fraction なら float 表示)
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以上の整数である必要があります。")
# 指定した精度に応じたスケール(例: precision=3 なら scale=1000)
scale = int(10**precision)
valid_candidates = [] # (整数部分, 許容されるfraction部の整数値リスト) のタプルを格納
# 整数部分の候補は、min_valの整数部分からmax_valの整数部分まで
for k in range(math.floor(min_val), math.floor(max_val) + 1):
# kを共通の整数部分とする場合の、小数部分がとれる範囲は
# r = 数値 - k が [r_lower, r_upper) に収まる必要がある
r_lower = max(0, min_val - k) # 下限
r_upper = min(1, max_val - k) # 上限(1未満にする)
# 小数部分は、指定精度で離散化された値 i/scale (i = 0, 1, ..., scale-1) として扱う
# i/scale が [r_lower, r_upper) に入る i を抽出します
allowed_i = [
i for i in range(scale) if (i / scale) >= r_lower and (i / scale) < r_upper
]
if allowed_i: # 少なくとも1つの値がとれるなら候補に追加
valid_candidates.append((k, allowed_i))
if not valid_candidates:
raise ValueError("指定された範囲内で共通の整数部分を持つ数値が生成できません。")
# 有効な候補からランダムに1つ選びます
k, allowed_i = random.choice(valid_candidates)
if len(allowed_i) < 2:
# もし候補が1種類しかなければ、異なる2つの数値を生成できません
raise ValueError("指定された範囲と精度では、異なる2つの数値を生成できません。")
# allowed_i から重複しない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)