|
|
import random
|
|
|
import re
|
|
|
from dataclasses import dataclass
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
|
def check_valid_arithmetic_expression(expression: str, result: int) -> bool:
|
|
|
"""
|
|
|
Check if a string is a valid arithmetic expression.
|
|
|
|
|
|
With format
|
|
|
|
|
|
num1 op1 num2 op2 num3 op3 num4
|
|
|
|
|
|
with operators +, -, *, /
|
|
|
|
|
|
Args:
|
|
|
expression: The expression to check
|
|
|
|
|
|
Returns:
|
|
|
bool: True if valid arithmetic expression, False otherwise
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pattern = (
|
|
|
r"^\s*(\d+)\s*([+\-*/])\s*(\d+)\s*([+\-*/])\s*(\d+)\s*([+\-*/])\s*(\d+)\s*$"
|
|
|
)
|
|
|
|
|
|
if bool(re.match(pattern, expression)):
|
|
|
return eval(expression) == result
|
|
|
return False
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class ArithmeticProblem:
|
|
|
num_1: int
|
|
|
num_2: int
|
|
|
num_3: int
|
|
|
num_4: int
|
|
|
op1: str
|
|
|
op2: str
|
|
|
op3: str
|
|
|
expression: str
|
|
|
result: int
|
|
|
|
|
|
|
|
|
class Mode(Enum):
|
|
|
ALL = "all"
|
|
|
MUL_DIV = "mul_div"
|
|
|
|
|
|
|
|
|
class ArithmeticProblemGenerator:
|
|
|
def __init__(
|
|
|
self,
|
|
|
min_num: int = 1,
|
|
|
max_num: int = 100,
|
|
|
result_min: int = 1,
|
|
|
result_max: int = 1000,
|
|
|
max_attempts: int = 100,
|
|
|
operators: tuple[str] = ("+", "-", "*", "/"),
|
|
|
mode: Mode = Mode.ALL,
|
|
|
):
|
|
|
"""
|
|
|
Initialize the arithmetic problem generator.
|
|
|
|
|
|
Args:
|
|
|
min_num: The minimum number to use in the arithmetic problem
|
|
|
max_num: The maximum number to use in the arithmetic problem
|
|
|
operators: The operators to use in the arithmetic problem
|
|
|
mode: The mode of the arithmetic problem
|
|
|
"""
|
|
|
self.min_num = min_num
|
|
|
self.max_num = max_num
|
|
|
self.result_min = result_min
|
|
|
self.result_max = result_max
|
|
|
self.operators = operators
|
|
|
self.max_attempts = max_attempts
|
|
|
self.mode = mode
|
|
|
|
|
|
def _generate_random_number(self) -> int:
|
|
|
return random.randint(self.min_num, self.max_num)
|
|
|
|
|
|
def _generate_random_operator(self) -> str:
|
|
|
return random.choice(self.operators)
|
|
|
|
|
|
def generate_problem(self) -> ArithmeticProblem:
|
|
|
"""
|
|
|
Generate an countdown arithmetic problem.
|
|
|
|
|
|
Generate four numbers, num_1, num_2, num_3, num_4,
|
|
|
and operators between them, and apply the operators to the numbers to get the result.
|
|
|
|
|
|
Make sure that the result must exactly be an integer, and
|
|
|
match the result of the arithmetic problem.
|
|
|
|
|
|
Returns:
|
|
|
ArithmeticProblem: The generated arithmetic problem
|
|
|
"""
|
|
|
max_attempts = 1_000
|
|
|
|
|
|
for _ in range(max_attempts):
|
|
|
|
|
|
num_1 = self._generate_random_number()
|
|
|
num_2 = self._generate_random_number()
|
|
|
num_3 = self._generate_random_number()
|
|
|
num_4 = self._generate_random_number()
|
|
|
|
|
|
|
|
|
op1 = self._generate_random_operator()
|
|
|
op2 = self._generate_random_operator()
|
|
|
op3 = self._generate_random_operator()
|
|
|
|
|
|
if (
|
|
|
self.mode == Mode.MUL_DIV
|
|
|
and op1 not in ("*", "/")
|
|
|
and op2 not in ("*", "/")
|
|
|
and op3 not in ("*", "/")
|
|
|
):
|
|
|
continue
|
|
|
|
|
|
|
|
|
result = self._evaluate_expression(
|
|
|
num_1, op1, num_2, op2, num_3, op3, num_4
|
|
|
)
|
|
|
|
|
|
|
|
|
if (
|
|
|
isinstance(result, (int, float))
|
|
|
and result == int(result)
|
|
|
and self.result_min <= result <= self.result_max
|
|
|
):
|
|
|
result = int(result)
|
|
|
return ArithmeticProblem(
|
|
|
num_1=num_1,
|
|
|
num_2=num_2,
|
|
|
num_3=num_3,
|
|
|
num_4=num_4,
|
|
|
op1=op1,
|
|
|
op2=op2,
|
|
|
op3=op3,
|
|
|
expression=f"{num_1} {op1} {num_2} {op2} {num_3} {op3} {num_4}",
|
|
|
result=result,
|
|
|
)
|
|
|
|
|
|
return None
|
|
|
|
|
|
def _evaluate_expression(
|
|
|
self,
|
|
|
num_1: int,
|
|
|
op1: str,
|
|
|
num_2: int,
|
|
|
op2: str,
|
|
|
num_3: int,
|
|
|
op3: str,
|
|
|
num_4: int,
|
|
|
) -> float:
|
|
|
"""
|
|
|
Evaluate the arithmetic expression following standard order of operations.
|
|
|
|
|
|
Args:
|
|
|
num_1: First number
|
|
|
op1: First operator
|
|
|
num_2: Second number
|
|
|
op2: Second operator
|
|
|
num_3: Third number
|
|
|
op3: Third operator
|
|
|
num_4: Fourth number
|
|
|
|
|
|
Returns:
|
|
|
float: The result of the arithmetic expression
|
|
|
"""
|
|
|
|
|
|
expression = f"{num_1} {op1} {num_2} {op2} {num_3} {op3} {num_4}"
|
|
|
|
|
|
|
|
|
|
|
|
return eval(expression)
|
|
|
|
|
|
|
|
|
class ArithmeticProblemDescriptionGenerator:
|
|
|
def __init__(self):
|
|
|
"""Initialize the description generator with various problem templates."""
|
|
|
self.problem_templates = [
|
|
|
|
|
|
"Using the numbers {num_1}, {num_2}, {num_3}, and {num_4}, create an expression that equals {result}. You can only use +, -, x, and / operators.",
|
|
|
"Can you make {result} using {num_1}, {num_2}, {num_3}, and {num_4}? Use only +, -, x, and / operators.",
|
|
|
"Find a way to combine {num_1}, {num_2}, {num_3}, and {num_4} to get {result} using only +, -, x, and / operators.",
|
|
|
"Use all four numbers ({num_1}, {num_2}, {num_3}, {num_4}) to make {result}. Only +, -, x, and / operators are allowed.",
|
|
|
|
|
|
"Given the numbers {num_1}, {num_2}, {num_3}, and {num_4}, arrange them with +, -, x, and / operators to achieve {result}.",
|
|
|
"Your task: Use {num_1}, {num_2}, {num_3}, and {num_4} exactly once each with only +, -, x, and / operators to create an expression equal to {result}.",
|
|
|
"Problem: How can you use the four numbers {num_1}, {num_2}, {num_3}, {num_4} with +, -, x, and / operators to get {result}?",
|
|
|
|
|
|
"Create a mathematical expression using {num_1}, {num_2}, {num_3}, and {num_4} that equals {result}. Only basic arithmetic operators (+, -, x, /) are permitted.",
|
|
|
"Arrange {num_1}, {num_2}, {num_3}, and {num_4} with +, -, x, and / to make {result}.",
|
|
|
"Using only addition (+), subtraction (-), multiplication (x), and division (/), combine {num_1}, {num_2}, {num_3}, and {num_4} to equal {result}.",
|
|
|
]
|
|
|
|
|
|
def generate_description(self, problem: ArithmeticProblem) -> tuple[str, int]:
|
|
|
"""
|
|
|
Generate a problem description for the given arithmetic problem.
|
|
|
|
|
|
Args:
|
|
|
problem: The ArithmeticProblem to generate description for
|
|
|
|
|
|
Returns:
|
|
|
tuple[str, int]: A tuple containing (problem_description, result)
|
|
|
"""
|
|
|
|
|
|
problem_template = random.choice(self.problem_templates)
|
|
|
|
|
|
|
|
|
problem_description = problem_template.format(
|
|
|
num_1=problem.num_1,
|
|
|
num_2=problem.num_2,
|
|
|
num_3=problem.num_3,
|
|
|
num_4=problem.num_4,
|
|
|
result=problem.result,
|
|
|
)
|
|
|
|
|
|
return problem_description, problem.result
|
|
|
|