From 5ed1c38ba75fa09d85858a00089e15cfcac4490b Mon Sep 17 00:00:00 2001 From: Avighna Basak Date: Sun, 29 Mar 2026 19:39:32 +0530 Subject: [PATCH] Add matrix and octal modules with tests --- exceptions.py | 48 +++++++++++++++++++ matrix.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ octal.py | 63 ++++++++++++++++++++++++ test_matrix.py | 82 +++++++++++++++++++++++++++++++ test_octal.py | 68 ++++++++++++++++++++++++++ 5 files changed, 389 insertions(+) create mode 100644 exceptions.py create mode 100644 matrix.py create mode 100644 octal.py create mode 100644 test_matrix.py create mode 100644 test_octal.py diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..3a45308 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,48 @@ +class calculatorerror(Exception): + def __init__(self, msg="calculator error"): + self.msg = msg + super().__init__(self.msg) + +class invalidinputerror(calculatorerror): + def __init__(self, msg="invalid input"): + super().__init__(msg) + +class zerodenominatorerror(calculatorerror): + def __init__(self, msg="denominator cannot be zero"): + super().__init__(msg) + +class invalidfractionerror(calculatorerror): + def __init__(self, msg="invalid fraction format"): + super().__init__(msg) + +class invalidcomplexerror(calculatorerror): + def __init__(self, msg="invalid complex number"): + super().__init__(msg) + +class invalidbinaryerror(calculatorerror): + def __init__(self, msg="invalid binary number"): + super().__init__(msg) + +class invalidoctalerror(calculatorerror): + def __init__(self, msg="invalid octal number"): + super().__init__(msg) + +class invalidhexerror(calculatorerror): + def __init__(self, msg="invalid hex number"): + super().__init__(msg) + +class invalidseterror(calculatorerror): + def __init__(self, msg="invalid set format"): + super().__init__(msg) + +class invalidmatrixerror(calculatorerror): + def __init__(self, msg="invalid matrix format"): + super().__init__(msg) + +class dimensionmismatcherror(calculatorerror): + def __init__(self, msg="matrix dimensions dont match"): + super().__init__(msg) + +class undefinedoperationerror(calculatorerror): + def __init__(self, msg="operation is undefined"): + super().__init__(msg) diff --git a/matrix.py b/matrix.py new file mode 100644 index 0000000..a5e54a2 --- /dev/null +++ b/matrix.py @@ -0,0 +1,128 @@ +import json +from exceptions import invalidmatrixerror, dimensionmismatcherror + + +def parse_matrix(s): + s = s.strip() + try: + mat = json.loads(s) + except (json.JSONDecodeError, ValueError): + raise invalidmatrixerror(f"cant parse matrix: '{s}'") + + if not isinstance(mat, list) or not mat: + raise invalidmatrixerror("matrix must be a non-empty list of lists") + + for row in mat: + if not isinstance(row, list): + raise invalidmatrixerror("each row must be a list") + for elem in row: + if not isinstance(elem, (int, float)): + raise invalidmatrixerror(f"element must be numeric, got: {elem}") + + row_len = len(mat[0]) + for i, row in enumerate(mat): + if len(row) != row_len: + raise invalidmatrixerror( + f"row 0 has {row_len} cols but row {i} has {len(row)}" + ) + + return mat + + +def format_matrix(mat): + rows = [] + for row in mat: + formatted = [] + for val in row: + if isinstance(val, float) and val == int(val): + formatted.append(str(int(val))) + else: + formatted.append(str(val)) + rows.append('[' + ','.join(formatted) + ']') + return '[' + ','.join(rows) + ']' + + +def get_dims(mat): + r = len(mat) + c = len(mat[0]) if r > 0 else 0 + return r, c + + +def add(a, b): + m1 = parse_matrix(a) + m2 = parse_matrix(b) + r1, c1 = get_dims(m1) + r2, c2 = get_dims(m2) + + if r1 != r2 or c1 != c2: + raise dimensionmismatcherror( + f"cant add ({r1}x{c1}) and ({r2}x{c2})" + ) + + result = [] + for i in range(r1): + row = [] + for j in range(c1): + row.append(m1[i][j] + m2[i][j]) + result.append(row) + + return format_matrix(result) + + +def subtract(a, b): + m1 = parse_matrix(a) + m2 = parse_matrix(b) + r1, c1 = get_dims(m1) + r2, c2 = get_dims(m2) + + if r1 != r2 or c1 != c2: + raise dimensionmismatcherror( + f"cant subtract ({r1}x{c1}) and ({r2}x{c2})" + ) + + result = [] + for i in range(r1): + row = [] + for j in range(c1): + row.append(m1[i][j] - m2[i][j]) + result.append(row) + + return format_matrix(result) + + +def multiply(a, b): + m1 = parse_matrix(a) + m2 = parse_matrix(b) + r1, c1 = get_dims(m1) + r2, c2 = get_dims(m2) + + if c1 != r2: + raise dimensionmismatcherror( + f"cant multiply: cols of first ({c1}) != rows of second ({r2})" + ) + + result = [] + for i in range(r1): + row = [] + for j in range(c2): + total = 0 + for k in range(c1): + total += m1[i][k] * m2[k][j] + row.append(total) + result.append(row) + + return format_matrix(result) + + +def transpose(a): + m = parse_matrix(a) + rows, cols = get_dims(m) + + result = [] + for j in range(cols): + row = [] + for i in range(rows): + row.append(m[i][j]) + result.append(row) + + return format_matrix(result) diff --git a/octal.py b/octal.py new file mode 100644 index 0000000..be7c7f4 --- /dev/null +++ b/octal.py @@ -0,0 +1,63 @@ +from exceptions import invalidoctalerror + + +def check_octal(o): + o = o.strip() + if not o: + raise invalidoctalerror("empty octal string") + for ch in o: + if ch not in '01234567': + raise invalidoctalerror(f"invalid digit: '{ch}'") + + +def octal_to_decimal(o): + o = o.strip() + check_octal(o) + return str(int(o, 8)) + + +def decimal_to_octal(d): + num = int(d.strip()) + if num < 0: + return '-' + oct(abs(num))[2:] + return oct(num)[2:] + + +def octal_add(a, b): + a, b = a.strip(), b.strip() + check_octal(a) + check_octal(b) + res = int(a, 8) + int(b, 8) + return oct(res)[2:] + + +def octal_subtract(a, b): + a, b = a.strip(), b.strip() + check_octal(a) + check_octal(b) + res = int(a, 8) - int(b, 8) + if res < 0: + return '-' + oct(abs(res))[2:] + return oct(res)[2:] + + +def octal_multiply(a, b): + a, b = a.strip(), b.strip() + check_octal(a) + check_octal(b) + res = int(a, 8) * int(b, 8) + return oct(res)[2:] + + +def sevens_complement(o): + o = o.strip() + check_octal(o) + return ''.join(str(7 - int(d)) for d in o) + + +def eights_complement(o): + o = o.strip() + check_octal(o) + sevens = sevens_complement(o) + res = int(sevens, 8) + 1 + return oct(res)[2:] diff --git a/test_matrix.py b/test_matrix.py new file mode 100644 index 0000000..3c38a00 --- /dev/null +++ b/test_matrix.py @@ -0,0 +1,82 @@ +import unittest +from matrix import add, subtract, multiply, transpose +from exceptions import invalidmatrixerror, dimensionmismatcherror + + +class testmatrix(unittest.TestCase): + + def test_matrix_add(self): + res = add('[[1,2],[3,4]]', '[[5,6],[7,8]]') + self.assertEqual(res, '[[6,8],[10,12]]') + + def test_matrix_add_single_element(self): + res = add('[[1]]', '[[2]]') + self.assertEqual(res, '[[3]]') + + def test_matrix_add_negative(self): + res = add('[[1,2],[3,4]]', '[[-1,-2],[-3,-4]]') + self.assertEqual(res, '[[0,0],[0,0]]') + + def test_matrix_add_dimension_mismatch(self): + with self.assertRaises(dimensionmismatcherror): + add('[[1,2]]', '[[1,2],[3,4]]') + + def test_matrix_subtract(self): + res = subtract('[[5,6],[7,8]]', '[[1,2],[3,4]]') + self.assertEqual(res, '[[4,4],[4,4]]') + + def test_matrix_subtract_to_zero(self): + res = subtract('[[3,4],[5,6]]', '[[3,4],[5,6]]') + self.assertEqual(res, '[[0,0],[0,0]]') + + def test_matrix_subtract_dimension_mismatch(self): + with self.assertRaises(dimensionmismatcherror): + subtract('[[1,2,3]]', '[[1,2]]') + + def test_matrix_multiply(self): + res = multiply('[[1,2],[3,4]]', '[[5,6],[7,8]]') + self.assertEqual(res, '[[19,22],[43,50]]') + + def test_matrix_multiply_identity(self): + res = multiply('[[1,0],[0,1]]', '[[5,6],[7,8]]') + self.assertEqual(res, '[[5,6],[7,8]]') + + def test_matrix_multiply_rectangular(self): + res = multiply('[[1,2,3]]', '[[4],[5],[6]]') + self.assertEqual(res, '[[32]]') + + def test_matrix_multiply_dimension_mismatch(self): + with self.assertRaises(dimensionmismatcherror): + multiply('[[1,2]]', '[[1,2]]') + + def test_matrix_transpose(self): + res = transpose('[[1,2],[3,4]]') + self.assertEqual(res, '[[1,3],[2,4]]') + + def test_matrix_transpose_rectangular(self): + res = transpose('[[1,2,3],[4,5,6]]') + self.assertEqual(res, '[[1,4],[2,5],[3,6]]') + + def test_matrix_transpose_single_row(self): + res = transpose('[[1,2,3]]') + self.assertEqual(res, '[[1],[2],[3]]') + + def test_matrix_transpose_single_col(self): + res = transpose('[[1],[2],[3]]') + self.assertEqual(res, '[[1,2,3]]') + + def test_invalid_matrix_format(self): + with self.assertRaises(invalidmatrixerror): + add('not_a_matrix', '[[1,2]]') + + def test_invalid_matrix_non_numeric(self): + with self.assertRaises(invalidmatrixerror): + add('[["a","b"]]', '[[1,2]]') + + def test_invalid_matrix_inconsistent_rows(self): + with self.assertRaises(invalidmatrixerror): + add('[[1,2],[3]]', '[[1,2],[3,4]]') + + +if __name__ == "__main__": + unittest.main() diff --git a/test_octal.py b/test_octal.py new file mode 100644 index 0000000..dd16b39 --- /dev/null +++ b/test_octal.py @@ -0,0 +1,68 @@ +import unittest +from octal import ( + octal_to_decimal, decimal_to_octal, octal_add, + octal_subtract, octal_multiply, sevens_complement, eights_complement +) +from exceptions import invalidoctalerror + + +class testoctal(unittest.TestCase): + + def test_octal_to_decimal(self): + self.assertEqual(octal_to_decimal('247'), '167') + + def test_octal_to_decimal_zero(self): + self.assertEqual(octal_to_decimal('0'), '0') + + def test_octal_to_decimal_seven(self): + self.assertEqual(octal_to_decimal('7'), '7') + + def test_decimal_to_octal(self): + self.assertEqual(decimal_to_octal('167'), '247') + + def test_decimal_to_octal_zero(self): + self.assertEqual(decimal_to_octal('0'), '0') + + def test_decimal_to_octal_eight(self): + self.assertEqual(decimal_to_octal('8'), '10') + + def test_octal_add(self): + self.assertEqual(octal_add('12', '15'), '27') + + def test_octal_add_with_carry(self): + self.assertEqual(octal_add('77', '1'), '100') + + def test_octal_subtract(self): + self.assertEqual(octal_subtract('27', '15'), '12') + + def test_octal_multiply(self): + self.assertEqual(octal_multiply('3', '4'), '14') + + def test_sevens_complement(self): + self.assertEqual(sevens_complement('247'), '530') + + def test_sevens_complement_zero(self): + self.assertEqual(sevens_complement('000'), '777') + + def test_eights_complement(self): + self.assertEqual(eights_complement('247'), '531') + + def test_invalid_octal_digit_8(self): + with self.assertRaises(invalidoctalerror): + octal_to_decimal('128') + + def test_invalid_octal_digit_9(self): + with self.assertRaises(invalidoctalerror): + octal_to_decimal('19') + + def test_invalid_octal_letters(self): + with self.assertRaises(invalidoctalerror): + octal_to_decimal('12A') + + def test_invalid_octal_empty(self): + with self.assertRaises(invalidoctalerror): + octal_to_decimal('') + + +if __name__ == "__main__": + unittest.main()