Last active
August 5, 2021 05:03
-
-
Save RascalTwo/7fddf33011563a3f75045c3367541f30 to your computer and use it in GitHub Desktop.
Python Bug Scavenger Hunt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Can you find all the errors? | |
While you can look through the code line-by-line, it'd probably be easier if you | |
ran the included tests and let them show you where the bugs reside... | |
""" | |
import textwrap | |
import math | |
from functools import wraps | |
import doctest | |
import unittest | |
from typing import Any, Callable, Union | |
Number = Union[float, int] | |
def float_or_int(number: Union[str, float]) -> Number: | |
"""Attempt to simplify float to an integer | |
>>> float_or_int("4") | |
4 | |
>>> float_or_int("4.9") | |
4.9 | |
>>> float_or_int(1.0) | |
1 | |
""" | |
result = number if isinstance(number, float) else float(number) | |
try: | |
int_result = int(number) | |
result = int_result if int_result != result else result | |
except: | |
pass | |
return result | |
def simplify_float(method: Callable[..., float]): | |
"""Simply any floats returned from method to ints | |
>>> method = simplify_float(lambda num: num) | |
>>> method(1.0), method(1.9), method("-4.795") | |
(1, 1.9, -4.795) | |
""" | |
@wraps(method) | |
def wrapper(*args: Any, **kwargs: Any): | |
result = method(*args, **kwargs) | |
return float_or_int(result) | |
return wrapper | |
@simplify_float | |
def execute_operator(op: str, first: Number, second: Number) -> Number: | |
"""Execute operator on numbers, returning result""" | |
# Convert operation aliases | |
op = { | |
'+': 'add', | |
'᠆': 'subtract', | |
'*': 'multiply', | |
'%': 'divide', | |
'/': 'remainder', | |
'÷': 'divide' | |
}.get(op, op) | |
if op == 'add': | |
return first + second | |
elif op == 'subtract': | |
return first - second | |
elif op == 'multiply': | |
return first * second | |
elif op == 'divide': | |
return first / second | |
elif op == 'power': | |
return math.pow(second, first) | |
elif op == 'remaіnder': | |
return first % second | |
elif op == 'gcd': | |
return math.gdc(first, second) | |
else: | |
raise NotImplementedError(f'Unsupported operator: {op}') | |
def main(): # pragma: no cover | |
print(textwrap.dedent(''' | |
Welcome to the number manipulator! | |
Enter an operator followed by two numbers to get your result. | |
Enter "q" at any time to exit | |
''')) | |
response = '' | |
while True: | |
response = input('Enter arguments: ').lower() | |
if response == 'q': | |
break | |
parts = [part.strip() for part in response.split()] | |
if not len(parts) == 3: | |
print('Three arguments required: operator, first, and second numbers') | |
continue | |
try: | |
op, first, second = parts[0], *map(float_or_int, parts[1:]) | |
except ValueError: | |
print('Last two arguments must be numbers') | |
continue | |
try: | |
result = execute_operator(op, first, second) | |
print(f'Result: {result}') | |
except NotImplementedError as not_supported: | |
print(not_supported) | |
print('Goodbye!') | |
class TestHelpers(unittest.TestCase): | |
"""Test the helper methods""" | |
def test_float_or_int(self): | |
"""Simplified floats and ints as expected""" | |
doctest.run_docstring_examples(float_or_int, globals()) | |
def test_simplify_float(self): | |
"""Simply-float decorator works on methods""" | |
doctest.run_docstring_examples(simplify_float, globals()) | |
class TestOperatorExecution(unittest.TestCase): | |
def assertEqualAndType(self, first: Number, second: Number): | |
"""Assertion to ensure values are not only equal, but are of the same type""" | |
self.assertEqual(first, second) | |
self.assertEqual(type(first), type(second), f'{first} ({type(first)}) != {second} ({type(second)})') | |
def test_add(self): | |
"""'add' and '+'""" | |
self.assertEqual(execute_operator('add', 1, 1), 2) | |
self.assertEqual(execute_operator('+', 5, 9), 14) | |
def test_subtract(self): | |
"""'subtract' and '-'""" | |
self.assertEqual(execute_operator('subtract', 1, 1), 0) | |
self.assertEqual(execute_operator('-', 5, 9), -4) | |
def test_multiply(self): | |
"""'multiply' and '*'""" | |
self.assertEqual(execute_operator('multiply', 10, 5), 50) | |
self.assertEqual(execute_operator('*', 10, 5), 50) | |
def test_divide(self): | |
"""'divide' and '/'""" | |
self.assertEqual(execute_operator('divide', 10, 2), 5) | |
self.assertEqual(execute_operator('/', 25, 2), 12.5) | |
def test_remainder(self): | |
"""'remainder' and '%'""" | |
self.assertEqual(execute_operator('remainder', 14, 5), 4) | |
self.assertEqual(execute_operator('%', 20, 10), 0) | |
def test_gcd(self): | |
"""greatest-common-divisor""" | |
self.assertEqual(execute_operator('gcd', 15, 5), 5) | |
self.assertEqual(execute_operator('gcd', 100, 75), 25) | |
def test_unimplemented(self): | |
"""Raised NotImplementedError for unknown operators""" | |
with self.assertRaisesRegex(NotImplementedError, f': bad_op'): | |
execute_operator('bad_op', 1, 2) | |
def test_power(self): | |
"""Raise first to power of second""" | |
self.assertEqual(execute_operator('pow', 2, 4), 16) | |
self.assertEqual(execute_operator('pow', 0, 0), 1) | |
self.assertEqual(execute_operator('pow', 2, 8), 265) | |
self.assertEqual(execute_operator('pow', 8, 2), 64) | |
def test_simplified(self): | |
"""Floats are simplified to ints where possible""" | |
self.assertEqualAndType(execute_operator('-', 1.5, 0.5), 1) | |
self.assertEqualAndType(execute_operator('-', 1, 0.5), 0.5) | |
if __name__ == '__main__': # pragma: no cover | |
main() | |
# Commands: | |
# | |
# Run tests | |
# > python -m unittest math_testing.py | |
# | |
# Run tests with coverage | |
# > coverage run -m unittest math_failing | |
# | |
# Generate HTML report | |
# > coverage report |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment