class Matrix:
    def __str__(self):
        result = ""
        for row in self.matrix:
            result += " ".join(str(x) for x in row) + "\n"
        return result
    
    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.order = (rows,columns)
        self.matrix = []

        for i in range(rows):
            row = []
            for j in range(columns):
                row.append(0)
            self.matrix.append(row)
        
    @classmethod
    def from_list(cls, data):
        rows = len(data)
        columns = len(data[0]) if rows > 0 else 0

        obj = cls(rows, columns)
        obj.matrix = data
        return obj
        
    def transpose(self):
        data = [[self.matrix[i][j] for i in range(self.rows)] for j in range(self.columns)]
        return Matrix.from_list(data)
        
    def zero(self):
        self.matrix = []
        self.matrix = [[0 for _ in range(self.columns)] for _ in range(self.rows)]

    def identity(self):
        self.matrix = []  # reset matrix

        for i in range(self.rows):
            row = []
            for j in range(self.columns):
                if (i!=j):
                    row.append(0)
                else:
                    row.append(1)
            self.matrix.append(row)
            
    def __getitem__(self, key):
        if isinstance(key, tuple):
            i, j = key
            return self.matrix[i][j]
        return self.matrix[key]

    def __setitem__(self, key, value):
        if isinstance(key, tuple):
            i, j = key
            self.matrix[i][j] = value
        else:
            self.matrix[key] = value
            
    def __add__(self, other):
        if self.rows != other.rows or self.columns != other.columns:
            raise ValueError("matrices cannot be added")

        data = [
            [self[i, j] + other[i, j] for j in range(self.columns)]
            for i in range(self.rows)
        ]

        return Matrix.from_list(data)
        
    def __sub__(self, other):
        if self.rows != other.rows or self.columns != other.columns:
            raise ValueError("matrices cannot be added")

        data = [
            [self[i, j] - other[i, j] for j in range(self.columns)]
            for i in range(self.rows)
        ]

        return Matrix.from_list(data)
        
    def __mul__(self, other):
        if isinstance(other, (int, float)):
            return Matrix.from_list([
                [self[i, j] * other for j in range(self.columns)]
                for i in range(self.rows)
            ])

        if self.columns != other.rows:
            raise ValueError("Matrix dimensions do not match")

        data = [
            [
                sum(self[i, k] * other[k, j] for k in range(self.columns))
                for j in range(other.columns)
            ]
            for i in range(self.rows)
        ]

        return Matrix.from_list(data)
        
    def det(self):
        if self.rows != self.columns:
            raise ValueError("determinant is only defined for square matrices")

        # 1x1
        if self.rows == 1:
            return self[0, 0]

        # 2x2
        if self.rows == 2:
            return self[0, 0]*self[1, 1] - self[0, 1]*self[1, 0]

        # nxn (recursive expansion)
        determinant = 0

        for j in range(self.columns):
            submatrix = []

            for i in range(1, self.rows):
                row = []
                for k in range(self.columns):
                    if k != j:
                        row.append(self[i, k])
                submatrix.append(row)

            sign = (-1) ** j
            determinant += Matrix.from_list(submatrix).det() * sign * self[0, j]
        return determinant
    
    def _minor(self, i, j):
        return Matrix.from_list([
            [self[r, c] for c in range(self.columns) if c != j]
            for r in range(self.rows) if r != i
        ])
        
    def _cofactor_matrix(self):
        data = []

        for i in range(self.rows):
            row = []
            for j in range(self.columns):
                minor = self._minor(i, j)
                sign = (-1) ** (i + j)
                row.append(sign * minor.det())
            data.append(row)

        return Matrix.from_list(data)
        
    def inverse(self):
        if self.rows != self.columns:
            raise ValueError("Inverse only defined for square matrices")

        det = self.det()

        if det == 0:
            raise ValueError("Matrix is singular (no inverse)")

        # Special case: 1x1
        if self.rows == 1:
            return Matrix.from_list([[1 / self[0, 0]]])

        cofactor = self._cofactor_matrix()
        adjoint = cofactor.transpose()

        # Multiply by 1/det
        return adjoint*(1 / det)
        
    def __truediv__(self, other):
        return self*other.inverse()
        
    def __pow__(self, other):
        if not isinstance(other, int):
            raise ValueError("exponent is not a number")
        else:
            p = Matrix(self.rows,self.columns)
            p.identity()
            for i in range(other):
                p *= self
            return p