'''Question objects''' from ast import literal_eval as l_e import sys import random import os from os import listdir from os.path import isfile, join import numpy as np from QuestionClasses.question_file_writer import QuestionFileWriter WARNING = '\033[93m' BOUND = (-20, 20) class Question: '''Class for holding question data''' q_type = 0 matrices_dict = {} answer_matrix = 0 operation = '' def __init__(self, data: list): '''Initialiser''' self.matrices_dict = {} self.set_constants(data) self.set_answer_data(data) self.set_question_data(data) q_writer = QuestionFileWriter(self.get_file_name(), self) q_writer.write_to() @classmethod def from_file(cls, file_name: str): '''Constructor to initialise from file''' try: with open(file_name, 'r') as f: data = f.readline().split('$') return cls(data) except FileNotFoundError as e: print(f'{WARNING}{e}') sys.exit(1) @classmethod def from_user_input(cls, data: list) -> 'Question': '''Constructor to initialise from user inputted values''' return cls(data) @classmethod def random_any(cls) -> 'Question': '''Constructor to initialise random question of any type''' data = random.choice([cls.random_add, cls.random_sub, cls.random_mult, cls.random_det, cls.random_inv]) return cls(data) @classmethod def random_add(cls) -> 'Question': '''Constructor to initialise random addition question''' data = [] dim = tuple(cls.random_list((2, 10), 2)) size = dim[0] * dim[1] matrix1 = cls.create_matrix_string('A', size, dim) matrix2 = cls.create_matrix_string('B', size, dim) answer_matrix = np.add(cls.list_to_np(matrix1, dim), cls.list_to_np(matrix2, dim)) answer_values = cls.np_to_list(answer_matrix) answer = [str(dim), str(answer_values)] data.extend((1, '%'.join(matrix1), '%'.join(matrix2), '+', '%'.join(answer))) return cls(data) @classmethod def random_sub(cls) -> 'Question': '''Constructor to initialise random subtraction question''' data = [] dim = tuple(cls.random_list((2, 10), 2)) size = dim[0] * dim[1] matrix1 = cls.create_matrix_string('A', size, dim) matrix2 = cls.create_matrix_string('B', size, dim) answer_matrix = np.subtract(cls.list_to_np(matrix1, dim), cls.list_to_np(matrix2, dim)) answer_values = cls.np_to_list(answer_matrix) answer = [str(dim), str(answer_values)] data.extend((2, '%'.join(matrix1), '%'.join(matrix2), '-', '%'.join(answer))) return cls(data) @classmethod def random_mult(cls) -> 'Question': '''Constructor to initialise random multiplication question''' data = [] dim1 = (random.randint(2, 5), random.randint(2, 5)) dim2 = (dim1[1], random.randint(2, 5)) matrix1 = cls.create_matrix_string('A', dim1[0] * dim1[1], dim1) matrix2 = cls.create_matrix_string('B', dim2[0] * dim2[1], dim2) answer_matrix = np.dot(cls.list_to_np(matrix1, dim1), cls.list_to_np(matrix2, dim2)) answer_values = cls.np_to_list(answer_matrix) answer = [str((dim1[0], dim2[1])), str(answer_values)] data.extend((3, '%'.join(matrix1), '%'.join(matrix2), '*', '%'.join(answer))) return cls(data) @classmethod def random_det(cls) -> 'Question': '''Constructor to initialise random determinant question''' data = [] i = random.randint(2, 3) dim = tuple(cls.random_list((i, i), 2)) size = dim[0] * dim[1] matrix1 = cls.create_matrix_string('A', size, dim) answer_matrix = np.linalg.det(cls.list_to_np(matrix1, dim)) answer_value = cls.np_to_list(answer_matrix) answer = ['(1, 1)', answer_value] data.extend((4, '%'.join(matrix1), 'det', '%'.join(answer))) return cls(data) @classmethod def random_inv(cls) -> 'Question': '''Constructor to initialise random inverse question''' data = [] i = random.randint(2, 3) dim = tuple(cls.random_list((i, i), 2)) size = dim[0] * dim[1] matrix1 = cls.create_matrix_string('A', size, dim) answer_matrix = np.linalg.inv(cls.list_to_np(matrix1, dim)) answer_values = cls.np_to_list(answer_matrix) answer = [str((dim[1], dim[0])), str(answer_values)] data.extend((5, '%'.join(matrix1), 'inv', '%'.join(answer))) return cls(data) @staticmethod def np_to_list(matrix: np.array) -> list: '''Convert numpy array to list''' return str(matrix.flatten().tolist()) @staticmethod def list_to_np(m: list, dim: tuple) -> np.array: '''Conver list to numpy array''' return np.array(l_e(m[2])).reshape(dim) @staticmethod def random_list(b: tuple, size: int) -> list: '''Create a list of random integers given a size''' return [random.randint(b[0], b[1]) for _ in range(size)] @staticmethod def create_matrix_string(name: str, size: int, dim: tuple) -> list: '''Create a string of matrix data''' matrix_values = Question.random_list(BOUND, size) return [name, str(dim), str(matrix_values)] def set_question_data(self, data: list): '''Set question data''' q_data = [] for i in range(1, len(data) - 2): q_data.append(data[i].split('%')) self.matrices_dict[q_data[i - 1][0]] = (np.array(l_e(q_data[i - 1][2])) .reshape(l_e(q_data[i - 1][1]))) def set_answer_data(self, data: list): '''Set answer data''' a_data = data[-1].split('%') self.answer_matrix = np.array(l_e(a_data[1])).reshape(l_e(a_data[0])) def set_constants(self, data: list): '''Set question constants''' self.q_type, self.operation = data[0], data[-2] def show_answer(self): '''Printing answer''' print(f'=\n{self.answer_matrix}\n') def show_matrices(self): '''Printing matrices''' for i, (key, matrix) in enumerate(self.matrices_dict.items()): print(f'{key}:\n{matrix}\n') if i != len(self.matrices_dict) - 1: print(f'{self.operation}\n') self.show_answer() def get_file_name(self) -> str: '''Get file name for new question''' path = self.get_path() file_numbers = self.get_file_numbers(path) next_file_number = max(list(map(int, file_numbers))) + \ 1 if len(file_numbers) > 0 else 1 self.num = next_file_number return f'{path}/q{next_file_number}.txt' @staticmethod def get_path() -> str: '''Get path to find existing questions''' file_dir = os.path.dirname(os.path.realpath('__file__')) return os.path.join(file_dir, 'questions') @staticmethod def get_file_numbers(path: str) -> list: '''Get the existing question file numbers''' file_names = [f for f in listdir(path) if isfile(join(path, f))] file_numbers = [] for name in file_names: file_numbers.append(''.join(c for c in name if c.isdigit())) return file_numbers if __name__ == "__main__": Question.random_any()