Given this BNF-style grammar write a Python function `evaluate(str)` that evaluates the expression <expression> ::= <term> | <expression> '+' <term> | <expression> '-' <term> <term> ::= <factor> | <term> '*' <factor> | <term> '/' <factor> <factor> ::= <number> | '(' <expression> ')' | <prefix-op> <factor> <prefix-op> ::= 'sin' | 'cos' <number> ::= <digit> | <number> <digit> <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' For example you can evaluate this sin(3 + 2 * 4) - cos(15) = -0.240302
Given this BNF-style grammar write a Python function `evaluate(str)` that evaluates the expression <expression> ::= <term> | <expression> '+' <term> | <expression> '-' <term> <term> ::= <factor> | <term> '*' <factor> | <term> '/' <factor> <factor> ::= <number> | '(' <expression> ')' | <prefix-op> <factor> <prefix-op> ::= 'sin' | 'cos' <number> ::= <digit> | <number> <digit> <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' For example you can evaluate this sin(3 + 2 * 4) - cos(15) = -0.240302
To solve this problem, we need to evaluate mathematical expressions that include basic arithmetic operations, parentheses, and trigonometric functions (sine and cosine). The expressions follow a specific grammar structure, which we need to parse and evaluate correctly. ### Approach 1. **Tokenization**: Convert the input string into a list of tokens. Tokens include numbers, operators (`+`, `-`, `*`, `/`), parentheses (`(` and `)`), and trigonometric functions (`sin` and `cos`). This step also skips any whitespace in the input. 2. **Recursive Descent Parsing**: Use a recursive descent parser to process the tokens according to the given grammar. This parser handles operator precedence and associativity by breaking down the expression into terms and factors. 3. **Evaluation**: Evaluate the parsed structure on-the-fly by applying the operations as they are parsed. This includes arithmetic operations and applying trigonometric functions to their respective arguments. ### Solution Code
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 | import math def evaluate(expr): tokens = tokenize(expr) pos = 0 # Current position in the tokens list def peek(): return tokens[pos] if pos < len(tokens) else None def consume(): nonlocal pos pos += 1 def parse_expression(): val = parse_term() while peek() in ('+', '-'): op = peek() consume() term_val = parse_term() if op == '+': val += term_val else: val -= term_val return val def parse_term(): val = parse_factor() while peek() in ('*', '/'): op = peek() consume() factor_val = parse_factor() if op == '*': val *= factor_val else: val /= factor_val return val def parse_factor(): token = peek() if token is None: raise ValueError("Unexpected end of input") if token == '(': consume() expr_val = parse_expression() if peek() != ')': raise ValueError("Missing closing parenthesis") consume() return expr_val elif token in ('sin', 'cos'): consume() func = math.sin if token == 'sin' else math.cos factor_val = parse_factor() return func(factor_val) elif token.isdigit(): consume() return int(token) else: raise ValueError(f"Unexpected token {token} in factor") result = parse_expression() if pos != len(tokens): raise ValueError("Unexpected tokens remaining") return result def tokenize(s): tokens = [] i = 0 n = len(s) while i < n: if s[i].isspace(): i += 1 continue elif s[i].isdigit(): j = i while j < n and s[j].isdigit(): j += 1 tokens.append(s[i:j]) i = j elif s[i].isalpha(): j = i while j < n and s[j].isalpha(): j += 1 word = s[i:j] if word not in ('sin', 'cos'): raise ValueError(f"Unknown function {word}") tokens.append(word) i = j elif s[i] in '+-*/()': tokens.append(s[i]) i += 1 else: raise ValueError(f"Invalid character '{s[i]}'") return tokens |
I extracted the following code from that output:
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 | import math def evaluate(expr): tokens = tokenize(expr) pos = 0 # Current position in the tokens list def peek(): return tokens[pos] if pos < len(tokens) else None def consume(): nonlocal pos pos += 1 def parse_expression(): val = parse_term() while peek() in ('+', '-'): op = peek() consume() term_val = parse_term() if op == '+': val += term_val else: val -= term_val return val def parse_term(): val = parse_factor() while peek() in ('*', '/'): op = peek() consume() factor_val = parse_factor() if op == '*': val *= factor_val else: val /= factor_val return val def parse_factor(): token = peek() if token is None: raise ValueError("Unexpected end of input") if token == '(': consume() expr_val = parse_expression() if peek() != ')': raise ValueError("Missing closing parenthesis") consume() return expr_val elif token in ('sin', 'cos'): consume() func = math.sin if token == 'sin' else math.cos factor_val = parse_factor() return func(factor_val) elif token.isdigit(): consume() return int(token) else: raise ValueError(f"Unexpected token {token} in factor") result = parse_expression() if pos != len(tokens): raise ValueError("Unexpected tokens remaining") return result def tokenize(s): tokens = [] i = 0 n = len(s) while i < n: if s[i].isspace(): i += 1 continue elif s[i].isdigit(): j = i while j < n and s[j].isdigit(): j += 1 tokens.append(s[i:j]) i = j elif s[i].isalpha(): j = i while j < n and s[j].isalpha(): j += 1 word = s[i:j] if word not in ('sin', 'cos'): raise ValueError(f"Unknown function {word}") tokens.append(word) i = j elif s[i] in '+-*/()': tokens.append(s[i]) i += 1 else: raise ValueError(f"Invalid character '{s[i]}'") return tokens |
Running the following program:
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 | import math def evaluate(expr): tokens = tokenize(expr) pos = 0 # Current position in the tokens list def peek(): return tokens[pos] if pos < len(tokens) else None def consume(): nonlocal pos pos += 1 def parse_expression(): val = parse_term() while peek() in ('+', '-'): op = peek() consume() term_val = parse_term() if op == '+': val += term_val else: val -= term_val return val def parse_term(): val = parse_factor() while peek() in ('*', '/'): op = peek() consume() factor_val = parse_factor() if op == '*': val *= factor_val else: val /= factor_val return val def parse_factor(): token = peek() if token is None: raise ValueError("Unexpected end of input") if token == '(': consume() expr_val = parse_expression() if peek() != ')': raise ValueError("Missing closing parenthesis") consume() return expr_val elif token in ('sin', 'cos'): consume() func = math.sin if token == 'sin' else math.cos factor_val = parse_factor() return func(factor_val) elif token.isdigit(): consume() return int(token) else: raise ValueError(f"Unexpected token {token} in factor") result = parse_expression() if pos != len(tokens): raise ValueError("Unexpected tokens remaining") return result def tokenize(s): tokens = [] i = 0 n = len(s) while i < n: if s[i].isspace(): i += 1 continue elif s[i].isdigit(): j = i while j < n and s[j].isdigit(): j += 1 tokens.append(s[i:j]) i = j elif s[i].isalpha(): j = i while j < n and s[j].isalpha(): j += 1 word = s[i:j] if word not in ('sin', 'cos'): raise ValueError(f"Unknown function {word}") tokens.append(word) i = j elif s[i] in '+-*/()': tokens.append(s[i]) i += 1 else: raise ValueError(f"Invalid character '{s[i]}'") return tokens answer = round(evaluate('sin(35+2*4)-cos(15)'), 5) expected = -0.07209 assert answer == expected, f'Wrong answer; got {answer} instead of {expected}' print('All tests passed') |
All tests passed
Testing if the previous output contains the string All tests passed
: True