|
|
|
|
@ -1,11 +1,20 @@
|
|
|
|
|
|
|
|
|
|
import random
|
|
|
|
|
import math
|
|
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
|
|
|
|
|
# 安全的表达式求值函数
|
|
|
|
|
def safe_eval(expression):
|
|
|
|
|
"""
|
|
|
|
|
Safely evaluates a mathematical expression, allowing access to specific math functions.
|
|
|
|
|
"""
|
|
|
|
|
allowed_names = {
|
|
|
|
|
"sin": math.sin,
|
|
|
|
|
"cos": math.cos,
|
|
|
|
|
"tan": math.tan,
|
|
|
|
|
"sqrt": math.sqrt,
|
|
|
|
|
"pi": math.pi,
|
|
|
|
|
}
|
|
|
|
|
try:
|
|
|
|
|
return eval(expression, {"__builtins__": None}, {})
|
|
|
|
|
return eval(expression, {"__builtins__": None}, allowed_names)
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
@ -19,9 +28,15 @@ class QuestionGenerator(ABC):
|
|
|
|
|
def generate_bunch(self, count):
|
|
|
|
|
questions = []
|
|
|
|
|
generated_texts = set()
|
|
|
|
|
while len(questions) < count:
|
|
|
|
|
# 增加一个保险措施,防止因某种原因卡在循环里
|
|
|
|
|
max_attempts = count * 10
|
|
|
|
|
attempts = 0
|
|
|
|
|
while len(questions) < count and attempts < max_attempts:
|
|
|
|
|
text, answer = self._create_question_and_answer()
|
|
|
|
|
if text not in generated_texts:
|
|
|
|
|
attempts += 1
|
|
|
|
|
# 确保题目是新的,且答案是有效的整数
|
|
|
|
|
if text not in generated_texts and answer is not None and answer == int(answer):
|
|
|
|
|
answer = int(answer)
|
|
|
|
|
options = self._generate_options(answer)
|
|
|
|
|
correct_index = options.index(answer)
|
|
|
|
|
|
|
|
|
|
@ -32,9 +47,10 @@ class QuestionGenerator(ABC):
|
|
|
|
|
def _generate_options(self, correct_answer):
|
|
|
|
|
options = {correct_answer}
|
|
|
|
|
while len(options) < 4:
|
|
|
|
|
offset = random.randint(-10, 10)
|
|
|
|
|
# 扩大随机偏移范围以适应更复杂的计算结果
|
|
|
|
|
offset = random.randint(-20, 20)
|
|
|
|
|
if offset == 0:
|
|
|
|
|
offset = random.choice([-1, 1]) * 10
|
|
|
|
|
offset = random.choice([-1, 1]) * random.randint(1, 20)
|
|
|
|
|
|
|
|
|
|
distractor = correct_answer + offset
|
|
|
|
|
options.add(distractor)
|
|
|
|
|
@ -49,40 +65,142 @@ class QuestionGenerator(ABC):
|
|
|
|
|
|
|
|
|
|
class PrimarySchoolGenerator(QuestionGenerator):
|
|
|
|
|
def _create_question_and_answer(self):
|
|
|
|
|
ops = ['+', '-', '*', '/']
|
|
|
|
|
op = random.choice(ops)
|
|
|
|
|
|
|
|
|
|
if op == '+':
|
|
|
|
|
num1, num2 = random.randint(1, 100), random.randint(1, 100)
|
|
|
|
|
expr = f"{num1} + {num2}"
|
|
|
|
|
elif op == '-':
|
|
|
|
|
num1, num2 = random.randint(1, 100), random.randint(1, 100)
|
|
|
|
|
if num1 < num2: num1, num2 = num2, num1
|
|
|
|
|
expr = f"{num1} - {num2}"
|
|
|
|
|
elif op == '*':
|
|
|
|
|
num1, num2 = random.randint(1, 20), random.randint(1, 20)
|
|
|
|
|
expr = f"{num1} * {num2}"
|
|
|
|
|
else: # op == '/'
|
|
|
|
|
divisor = random.randint(2, 10)
|
|
|
|
|
answer = random.randint(2, 20)
|
|
|
|
|
dividend = divisor * answer
|
|
|
|
|
expr = f"{dividend} / {divisor}"
|
|
|
|
|
|
|
|
|
|
return expr, int(safe_eval(expr))
|
|
|
|
|
|
|
|
|
|
class MiddleSchoolGenerator(PrimarySchoolGenerator):
|
|
|
|
|
"""
|
|
|
|
|
生成包含 +, -, *, / 和括号的小学题目,确保答案是整数。
|
|
|
|
|
模板示例: a + (b * c) 或 a * (b - c)
|
|
|
|
|
"""
|
|
|
|
|
while True:
|
|
|
|
|
op1 = random.choice(['+', '-', '*'])
|
|
|
|
|
op2 = random.choice(['+', '-', '*', '/'])
|
|
|
|
|
|
|
|
|
|
num_a, num_b, num_c = random.randint(1, 10), random.randint(2, 10), random.randint(2, 10)
|
|
|
|
|
|
|
|
|
|
# 优先处理除法,确保可以整除
|
|
|
|
|
if op2 == '/':
|
|
|
|
|
inner_result = random.randint(2, 10)
|
|
|
|
|
num_b = inner_result * num_c
|
|
|
|
|
else:
|
|
|
|
|
inner_result = safe_eval(f"{num_b} {op2} {num_c}")
|
|
|
|
|
|
|
|
|
|
# 避免减法出现负数
|
|
|
|
|
if op1 == '-' and num_a < inner_result:
|
|
|
|
|
num_a, inner_result = inner_result + random.randint(1,10), num_a
|
|
|
|
|
|
|
|
|
|
expr_text = f"{num_a} {op1} ({num_b} {op2} {num_c})"
|
|
|
|
|
answer = safe_eval(expr_text)
|
|
|
|
|
|
|
|
|
|
if answer is not None and answer == int(answer):
|
|
|
|
|
return expr_text, int(answer)
|
|
|
|
|
|
|
|
|
|
class MiddleSchoolGenerator(QuestionGenerator):
|
|
|
|
|
def _create_question_and_answer(self):
|
|
|
|
|
if random.choice([True, False]):
|
|
|
|
|
return super()._create_question_and_answer()
|
|
|
|
|
else:
|
|
|
|
|
base = random.randint(2, 10)
|
|
|
|
|
expr = f"{base}**2"
|
|
|
|
|
return expr, int(safe_eval(expr))
|
|
|
|
|
"""
|
|
|
|
|
生成至少包含一个平方或开方,且有多个运算数的初中题目。
|
|
|
|
|
模板示例: a * (b**2 - c) 或 (a + sqrt(b)) / c
|
|
|
|
|
"""
|
|
|
|
|
while True:
|
|
|
|
|
# 随机选择一个模板
|
|
|
|
|
template = random.choice(['op_first', 'op_second'])
|
|
|
|
|
|
|
|
|
|
if template == 'op_first': # 特殊运算在括号内
|
|
|
|
|
op1 = random.choice(['+', '-', '*'])
|
|
|
|
|
num_a = random.randint(2, 20)
|
|
|
|
|
|
|
|
|
|
if random.choice([True, False]): # 使用平方
|
|
|
|
|
base = random.randint(2, 10)
|
|
|
|
|
op2 = random.choice(['+', '-'])
|
|
|
|
|
num_c = random.randint(1, 20)
|
|
|
|
|
inner_expr = f"{base}**2 {op2} {num_c}"
|
|
|
|
|
else: # 使用开方
|
|
|
|
|
perfect_squares = [4, 9, 16, 25, 36, 49, 64, 81, 100]
|
|
|
|
|
base = random.choice(perfect_squares)
|
|
|
|
|
op2 = random.choice(['+', '-', '*'])
|
|
|
|
|
num_c = random.randint(2, 10)
|
|
|
|
|
inner_expr = f"sqrt({base}) {op2} {num_c}"
|
|
|
|
|
|
|
|
|
|
expr_text = f"{num_a} {op1} ({inner_expr})"
|
|
|
|
|
|
|
|
|
|
else: # op_second, 特殊运算在括号外
|
|
|
|
|
op1 = random.choice(['+', '-', '*'])
|
|
|
|
|
num_c = random.randint(2, 15)
|
|
|
|
|
|
|
|
|
|
num_a, num_b = random.randint(1, 10), random.randint(1, 10)
|
|
|
|
|
inner_expr = f"({num_a} + {num_b})"
|
|
|
|
|
|
|
|
|
|
if random.choice([True, False]): # 使用平方
|
|
|
|
|
expr_text = f"{inner_expr}**2 {op1} {num_c}"
|
|
|
|
|
else: # 使用开方
|
|
|
|
|
# 确保括号内的和是平方数
|
|
|
|
|
target_sum = random.choice([4, 9, 16, 25, 36, 49, 64, 81, 100])
|
|
|
|
|
num_a = random.randint(1, target_sum - 1)
|
|
|
|
|
num_b = target_sum - num_a
|
|
|
|
|
expr_text = f"sqrt({num_a} + {num_b}) {op1} {num_c}"
|
|
|
|
|
|
|
|
|
|
# 检查表达式和答案
|
|
|
|
|
eval_expr = expr_text.replace('sqrt', 'math.sqrt')
|
|
|
|
|
answer = safe_eval(expr_text)
|
|
|
|
|
|
|
|
|
|
if answer is not None and answer == int(answer):
|
|
|
|
|
return expr_text, int(answer)
|
|
|
|
|
|
|
|
|
|
class HighSchoolGenerator(MiddleSchoolGenerator):
|
|
|
|
|
def _create_question_and_answer(self):
|
|
|
|
|
# 简化版高中题,可扩展为三角函数等
|
|
|
|
|
return super()._create_question_and_answer()
|
|
|
|
|
def _create_question_and_answer(self):
|
|
|
|
|
"""
|
|
|
|
|
生成至少包含一个sin, cos, tan,且有多个运算数的高中题目。
|
|
|
|
|
通过组合已知结果的三角函数来确保最终答案是整数。
|
|
|
|
|
"""
|
|
|
|
|
while True:
|
|
|
|
|
# (显示文本, 求值表达式, 值)
|
|
|
|
|
trig_facts = [
|
|
|
|
|
("sin(0)", "sin(0)", 0), ("cos(0)", "cos(0)", 1),
|
|
|
|
|
("sin(π/6)", "sin(pi/6)", 0.5), ("cos(π/3)", "cos(pi/3)", 0.5),
|
|
|
|
|
("tan(π/4)", "tan(pi/4)", 1), ("sin(π/2)", "sin(pi/2)", 1),
|
|
|
|
|
("cos(π)", "cos(pi)", -1),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
fact1_text, fact1_expr, fact1_val = random.choice(trig_facts)
|
|
|
|
|
|
|
|
|
|
# 随机选择一个更复杂的模板
|
|
|
|
|
template = random.choice(['template1', 'template2'])
|
|
|
|
|
|
|
|
|
|
if template == 'template1':
|
|
|
|
|
# 模板: A * (B op trig_func)
|
|
|
|
|
num_a = random.randint(2, 10)
|
|
|
|
|
num_b = random.randint(5, 20)
|
|
|
|
|
op = random.choice(['+', '-'])
|
|
|
|
|
|
|
|
|
|
# 如果三角函数值为0.5,需要特殊处理确保括号内结果易于计算
|
|
|
|
|
if fact1_val == 0.5 and op == '+':
|
|
|
|
|
# 让A成为偶数,确保最终结果是整数
|
|
|
|
|
num_a = random.randint(1, 5) * 2
|
|
|
|
|
|
|
|
|
|
expr_text = f"{num_a} * ({num_b} {op} {fact1_text})"
|
|
|
|
|
expr_eval = f"{num_a} * ({num_b} {op} {fact1_expr})"
|
|
|
|
|
|
|
|
|
|
else: # template2
|
|
|
|
|
# 模板: (A op trig_func1) * (B op trig_func2)
|
|
|
|
|
fact2_text, fact2_expr, fact2_val = random.choice(trig_facts)
|
|
|
|
|
num_a = random.randint(5, 15)
|
|
|
|
|
num_b = random.randint(2, 10)
|
|
|
|
|
op1 = random.choice(['+', '-'])
|
|
|
|
|
op2 = random.choice(['+', '-'])
|
|
|
|
|
|
|
|
|
|
# 确保两个括号内的值都不是分数,或者它们相乘后是整数
|
|
|
|
|
term1_val = num_a + (fact1_val if op1 == '+' else -fact1_val)
|
|
|
|
|
term2_val = num_b + (fact2_val if op2 == '+' else -fact2_val)
|
|
|
|
|
|
|
|
|
|
# 如果其中一个term含有.5,让另一个term的系数是偶数
|
|
|
|
|
if term1_val != int(term1_val):
|
|
|
|
|
num_b = random.randint(1, 5) * 2
|
|
|
|
|
elif term2_val != int(term2_val):
|
|
|
|
|
num_a = random.randint(1, 5) * 2
|
|
|
|
|
|
|
|
|
|
expr_text = f"({num_a} {op1} {fact1_text}) * ({num_b} {op2} {fact2_text})"
|
|
|
|
|
expr_eval = f"({num_a} {op1} {fact1_expr}) * ({num_b} {op2} {fact2_expr})"
|
|
|
|
|
|
|
|
|
|
answer = safe_eval(expr_eval)
|
|
|
|
|
if answer is not None and answer == int(answer):
|
|
|
|
|
return expr_text, int(answer)
|
|
|
|
|
|
|
|
|
|
def get_generator(level):
|
|
|
|
|
generators = {
|
|
|
|
|
|