- Notifications
You must be signed in to change notification settings - Fork 46
Calculator Exercise#146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:master
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Calculator Exercise #146
Changes from all commits
42a4ee9c3a57baeafc74ceb5061a8a27fe3f0587c5926a1e5e4cc0e8403fcb1a324e6d56485ad88977256c5da7344f85c72617ba6ac5eef1File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| """Calculator module. | ||
yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| This module allows evaluation of strings which represent mathematical | ||
| expressions. The supported operators are declared in operators dictionary. | ||
| """ | ||
| import re | ||
| import math | ||
yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| import operator | ||
| from sys import maxsize | ||
| from functools import partial | ||
| from collections import namedtuple | ||
| ANY_NUMBER = r"[-+]?\d+(?:\.\d+)?" | ||
| # higher precedence means the operator is stronger (will be done first) | ||
| Operator = namedtuple("Operator", ["operation", "precedence", "regex"]) | ||
yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| OPERATORS = [ | ||
| Operator(precedence=1, operation=operator.add, | ||
| regex=fr"({ANY_NUMBER})\+({ANY_NUMBER})"), | ||
| Operator(precedence=1, operation=operator.sub, | ||
| regex=fr"({ANY_NUMBER})-({ANY_NUMBER})"), | ||
| Operator(precedence=2, operation=operator.mul, | ||
| regex=fr"({ANY_NUMBER})\*({ANY_NUMBER})"), | ||
| Operator(precedence=2, operation=operator.truediv, | ||
| regex=fr"({ANY_NUMBER})/({ANY_NUMBER})"), | ||
| Operator(precedence=3, operation=math.pow, | ||
| regex=fr"({ANY_NUMBER})\^({ANY_NUMBER})"), | ||
| Operator(precedence=4, operation=math.fmod, | ||
| regex=fr"({ANY_NUMBER})%({ANY_NUMBER})"), | ||
| Operator(precedence=5, operation=lambda x, y: (x + y) / 2.0, | ||
| regex=fr"({ANY_NUMBER})@({ANY_NUMBER})"), | ||
| Operator(precedence=5, operation=max, | ||
| regex=fr"({ANY_NUMBER})\$({ANY_NUMBER})"), | ||
| Operator(precedence=5, operation=min, | ||
| regex=fr"({ANY_NUMBER})&({ANY_NUMBER})"), | ||
| Operator(precedence=6, operation=operator.neg, | ||
| regex=fr"~({ANY_NUMBER})"), | ||
| Operator(precedence=7, operation=math.factorial, | ||
| regex=fr"({ANY_NUMBER})!"), | ||
| Operator(precedence=maxsize, operation=lambda expr: evaluate(expr), | ||
| regex=r"\(([^()]*)\)") | ||
| ] | ||
| def evaluate(expression: str) -> float: | ||
| """Calculate the value of a mathematical expression. | ||
| Args: | ||
| expression (str): the entire mathematical expression to calculate. | ||
| Returns: | ||
| float. The calculated result of the given expression. | ||
| """ | ||
| expression = expression.replace(" ", "") | ||
| operators_by_precedence = sorted(OPERATORS, reverse=True, | ||
| key=operator.attrgetter("precedence")) | ||
| for operator_info in operators_by_precedence: | ||
| operator_regex = operator_info.regex | ||
| search_result = re.search(operator_regex, str(expression)) | ||
| if search_result is not None: | ||
| operands = search_result.groups() | ||
| if all(re.fullmatch(ANY_NUMBER, op) for op in operands): | ||
| operands = [float(op) for op in operands] | ||
| operation_result = operator_info.operation(*operands) | ||
| new_sub_expr = str(operation_result) | ||
| if operation_result > 0: | ||
| new_sub_expr = f"+{operation_result}" | ||
| old_expression = search_result.group() | ||
| expression = expression.replace(old_expression, new_sub_expr) | ||
| return evaluate(expression) | ||
| return float(expression) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. newline (conventions) yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| def main(): | ||
| msg_user = "Enter expression to evaluate ('quit' to exit) >>> " | ||
| for expression in iter(partial(input, msg_user), "quit"): | ||
| print(evaluate(expression)) | ||
| if __name__ == '__main__': | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """Unit test module for testing calculator module.""" | ||
| import unittest | ||
| from calculator import evaluate | ||
| class TestOperations(unittest.TestCase): | ||
| """Test the supported operations by the calculator.""" | ||
| def test_simple_equations(self): | ||
| """Test basic expressions.""" | ||
| self.assertEqual(evaluate("1 + (2 + 3)"), 6.0) | ||
| self.assertEqual(evaluate("(8 - 4) - 6"), -2.0) | ||
| self.assertEqual(evaluate("3* -5"), -15.0) | ||
| self.assertEqual(evaluate("65 /5"), 13.0) | ||
| self.assertEqual(evaluate("9 ^ 0.5"), 3.0) | ||
| self.assertEqual(evaluate("3 % 2"), 1.0) | ||
| self.assertEqual(evaluate("15 @ 60"), 37.5) | ||
| self.assertEqual(evaluate("13 $ 16"), 16.0) | ||
| self.assertEqual(evaluate("13 & 16"), 13.0) | ||
| self.assertEqual(evaluate("~9"), -9.0) | ||
| self.assertEqual(evaluate("5 !"), 120.0) | ||
| def test_precedence(self): | ||
| """Test the order which operations are done.""" | ||
| self.assertEqual(evaluate("((1 - 4)^3)*-1"), 27.0) | ||
| self.assertEqual(evaluate("((1 + 2) * 9 + 4! + ~-2)"), 53.0) | ||
| self.assertEqual(evaluate("1 + 2 * 3 + 4 / 0.5 $ (1 / 3)"), 15.0) | ||
| self.assertEqual(evaluate("(0.3 + (4@0.5))$(~0.25 + ((3^-2)@0.5))"), | ||
| 2.55) | ||
| class TestEdgeCases(unittest.TestCase): | ||
| """Test rare cases of inputs to see function behavior.""" | ||
| def test_wrong_input(self): | ||
| # un-supported operator | ||
| with self.assertRaises(ValueError): | ||
| evaluate("1#1") | ||
| # should be "55*0.2" | ||
| with self.assertRaises(ValueError): | ||
| evaluate("55*.2") | ||
| # should be "~-1" | ||
| with self.assertRaises(ValueError): | ||
| evaluate("--1") | ||
| with self.assertRaises(ValueError): | ||
| evaluate("(((8@5.67) + 1)") | ||
| with self.assertRaises(ValueError): | ||
| evaluate('1+a*76') | ||
| self.assertEqual(evaluate("-1-2*-2"), 3.0) | ||
yonatanevantal marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if __name__ == '__main__': | ||
| unittest.main() | ||
Uh oh!
There was an error while loading. Please reload this page.