File size: 8,237 Bytes
00db46c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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

    """
    # Regex pattern for: number operator number operator number operator number
    # Number can only be positive integer (no negative numbers)
    # Operators are +, -, *, /
    # Spacing between numbers and operators is optional
    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"  # All operators are allowed
    MUL_DIV = "mul_div"  # Only multiplication and division are allowed


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):
            # Generate four random numbers
            num_1 = self._generate_random_number()
            num_2 = self._generate_random_number()
            num_3 = self._generate_random_number()
            num_4 = self._generate_random_number()

            # Generate three random operators for the expression: num_1 op1 num_2 op2 num_3 op3 num_4
            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

            # Try to evaluate the expression and ensure it results in an integer
            result = self._evaluate_expression(
                num_1, op1, num_2, op2, num_3, op3, num_4
            )

            # Check if result is an integer (no floating point remainder)
            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

        """
        # Build expression string: num_1 op1 num_2 op2 num_3 op3 num_4
        expression = f"{num_1} {op1} {num_2} {op2} {num_3} {op3} {num_4}"

        # Use eval to calculate the result (following Python's order of operations)
        # This handles operator precedence correctly (* and / before + and -)
        return eval(expression)


class ArithmeticProblemDescriptionGenerator:
    def __init__(self):
        """Initialize the description generator with various problem templates."""
        self.problem_templates = [
            # Direct challenge 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.",
            # Instructional templates
            "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}?",
            # Additional templates with operator emphasis
            "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)

        """
        # Select random template
        problem_template = random.choice(self.problem_templates)

        # Generate the problem description
        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