Skip to content

Instantly share code, notes, and snippets.

@RascalTwo
Last active August 5, 2021 05:03
Show Gist options
  • Save RascalTwo/7fddf33011563a3f75045c3367541f30 to your computer and use it in GitHub Desktop.
Save RascalTwo/7fddf33011563a3f75045c3367541f30 to your computer and use it in GitHub Desktop.
Python Bug Scavenger Hunt
"""
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