diff --git a/calculator.py b/calculator.py index 5daac9a..746cae1 100644 --- a/calculator.py +++ b/calculator.py @@ -1,11 +1,43 @@ +from set import SetOperations + + class Calculator: + def __init__(self): + self.set_ops = SetOperations() + def add(self, a, b): return a + b + def subtract(self, a, b): return a - b + def multiply(self, a, b): return a * b + def divide(self, a, b): if b == 0: raise ValueError("Division by zero") return a / b + + def set_union(self, left, right): + return self.set_ops.union(left, right) + + def set_intersection(self, left, right): + return self.set_ops.intersection(left, right) + + def set_difference(self, left, right): + return self.set_ops.difference(left, right) + + def set_symmetric_difference(self, left, right): + return self.set_ops.symmetric_difference(left, right) + + def set_is_subset(self, left, right): + return self.set_ops.is_subset(left, right) + + def set_is_superset(self, left, right): + return self.set_ops.is_superset(left, right) + + def evaluate(self, expression, mode="set"): + if mode != "set": + raise ValueError("Only set mode is supported in this repository state") + return self.set_ops.evaluate_expression(expression) diff --git a/set.py b/set.py new file mode 100644 index 0000000..dc788d5 --- /dev/null +++ b/set.py @@ -0,0 +1,69 @@ +import ast + + +class SetOperations: + def parse_set(self, value): + if isinstance(value, set): + return value + + if not isinstance(value, str): + raise ValueError("Set input must be a string or set") + + text = value.strip() + if text == "{}": + return set() + + try: + parsed = ast.literal_eval(text) + except (SyntaxError, ValueError) as exc: + raise ValueError("Invalid set input") from exc + + if isinstance(parsed, (set, list, tuple)): + return set(parsed) + + raise ValueError("Set input must be a set, list, or tuple") + + def union(self, left, right): + return self.parse_set(left) | self.parse_set(right) + + def intersection(self, left, right): + return self.parse_set(left) & self.parse_set(right) + + def difference(self, left, right): + return self.parse_set(left) - self.parse_set(right) + + def symmetric_difference(self, left, right): + return self.parse_set(left) ^ self.parse_set(right) + + def is_subset(self, left, right): + return self.parse_set(left).issubset(self.parse_set(right)) + + def is_superset(self, left, right): + return self.parse_set(left).issuperset(self.parse_set(right)) + + def evaluate_expression(self, expression): + if not isinstance(expression, str) or not expression.strip(): + raise ValueError("Expression must be a non-empty string") + + text = expression.strip() + lowered = text.lower() + + operations = [ + (" union ", self.union), + (" intersection ", self.intersection), + (" difference ", self.difference), + (" symdiff ", self.symmetric_difference), + (" subset ", self.is_subset), + (" superset ", self.is_superset), + ] + + for marker, function in operations: + if marker in lowered: + idx = lowered.index(marker) + left = text[:idx].strip() + right = text[idx + len(marker):].strip() + if not left or not right: + raise ValueError("Invalid set expression") + return function(left, right) + + raise ValueError("Invalid set expression") diff --git a/test_calculator.py b/test_calculator.py index 7cb79e9..c6f39ee 100644 --- a/test_calculator.py +++ b/test_calculator.py @@ -1,6 +1,7 @@ import unittest from calculator import Calculator + class TestCalculator(unittest.TestCase): # base test cases def setUp(self): @@ -18,16 +19,49 @@ def test_multiply(self): def test_divide(self): self.assertEqual(self.calc.divide(2, 4), 0.5) - def test_divide(self): + def test_divide_negative(self): self.assertEqual(self.calc.divide(4, -2), -2) - - def test_divide_fail(self): # this will fail + + def test_divide_fail(self): self.assertNotEqual(self.calc.divide(4, -2), 2) def test_divide_by_zero(self): with self.assertRaises(ValueError): self.calc.divide(5, 0) -# Optional: this allows running the script directly - if __name__ == '__main__': - unittest.main() # + # set operation test cases + def test_set_union(self): + self.assertEqual(self.calc.set_union("{1,2}", "{2,3}"), {1, 2, 3}) + + def test_set_intersection(self): + self.assertEqual(self.calc.set_intersection("{1,2,3}", "{2,4}"), {2}) + + def test_set_difference(self): + self.assertEqual(self.calc.set_difference("{1,2,3}", "{2}"), {1, 3}) + + def test_set_symmetric_difference(self): + self.assertEqual(self.calc.set_symmetric_difference("{1,2,3}", "{2,4}"), {1, 3, 4}) + + def test_set_subset(self): + self.assertTrue(self.calc.set_is_subset("{1,2}", "{1,2,3}")) + + def test_set_superset(self): + self.assertTrue(self.calc.set_is_superset("{1,2,3}", "{1,2}")) + + def test_set_empty_set_boundary(self): + self.assertEqual(self.calc.set_union("{}", "{1,2}"), {1, 2}) + + def test_set_invalid_input(self): + with self.assertRaises(ValueError): + self.calc.set_union("{1,2}", "not_a_set") + + def test_set_expression_union(self): + self.assertEqual(self.calc.evaluate("{1,2} union {2,3}"), {1, 2, 3}) + + def test_set_expression_invalid_operator(self): + with self.assertRaises(ValueError): + self.calc.evaluate("{1,2} merge {2,3}") + + +if __name__ == '__main__': + unittest.main()