pythonarrayssudoku

Two-dimensions array 9x9 of integers without using numpy.array (subclassing MutableSequence)


Few days ago I found the next example of howto implement customs list classes via subclassing the MutableSequence from collections.abc.

class TypedList(MutableSequence):

    def __init__(self, oktypes, *args):
        self.oktypes = oktypes
        self.list = list()
        self.extend(list(args))

    def check(self, v):
        if not isinstance(v, self.oktypes):
            raise TypeError(v)

    def __len__(self): return len(self.list)

    def __getitem__(self, i): return self.list[i]

    def __delitem__(self, i): del self.list[i]

    def __setitem__(self, i, v):
        self.check(v)
        self.list[i] = v

    def insert(self, i, v):
        self.check(v)
        self.list.insert(i, v)

    def __str__(self):
        return str(self.list)

Example of use of TypedList:

    tl = TypedList(int)
    # ok
    tl.append(1)
    # next TypeError will be raised
    tl.append('1')

My Question:

I was wondering if there is way of implementing an two-dimensions SudokuArray(MutableSequence) class in similar manner for mananing a sudoku array in a game?

For giving you an idea, below you can see a possible implementation (non-operative) of that SudokuArray(MutableSequence):

class SudokuArray(MutableSequence):

    def __init__(self, n_rows=9, n_columns=9, init_value=None):
        self.n_rows = n_rows
        self.n_columns = n_columns
        self.array = [[init_value for _ in range(0, self.n_columns, 1)] for _ in range(0, self.n_rows, 1)]

    def check(self, row_number, column_number, number):
        if number in self.get_row_values(row_number) or
                                         number in self.get_column_values(column_number) or
                                         number in self.get_nonet_values(row_number, column_number)):
            raise ExistentNumberError("Existent number in row, column or nonet",
                                      number, row_number, column_number)

    def __len__(self): return self.n_rows, self.n_columns

    def __getitem__(self, row_number, column_number): return self.array[column_number][row_number]

    def __delitem__(self, row_number, column_number): del self.array[column_number][row_number]

    def __setitem__(self, row_number, column_number, number):
        self.check(number, row_number, column_number)
        self.array[column_number][row_number] = number

    def insert(self, row_number, column_number, number):
        self.check(row_number, column_number, number)
        self.array.insert(row_number, column_number, number)

    def __str__(self):
        return str(self.array)

    def get_row_values(self, row_number):
        # todo implement get_row_values method
        pass

    def get_column_values(self, column_number):
        # todo implement get_column_values method
        pass

    def get_nonet_values(self, row_number, column_number):
        # todo implement get_nonet_values method
        pass

class ExistentNumberError(Exception):
    def __init__(self, message, number, row_number, column_number):
        super().__init__(message)
        self.number = number
        self.row_number = row_number
        self.column_number = column_number

Example of use of SudokuArray:

    sudoku_array = SudokuArray()
    sudoku_array[0][0] = 1
    # next ExistentNumberError will be raised
    sudoku_array[0][0] = 1

Of course I could use numpy.array but I think our 9x9 array is very simple for using numpy. Also I want to avoid dependencies.

Any idea?


Solution

  • There are these issues in your attempt:

    Here is the suggested code:

    from collections.abc import MutableSequence
    from math import isqrt
    
    class SudokuArray(MutableSequence):
    
        def __init__(self, size=9, init_value=None):
            self.size = size
            self.array = [[init_value for _ in range(self.size)] for _ in range(self.size)]
    
        def check(self, row_number, column_number, number):
            if (number in self.get_row_values(row_number) or
                      number in self.get_column_values(column_number) or
                      number in self.get_nonet_values(row_number, column_number)):
                raise ExistentNumberError("Existent number in row, column or nonet",
                                          number, row_number, column_number)
    
        def __len__(self):
            return self.size * self.size
    
        def __getitem__(self, coordinates): 
            return self.array[coordinates[0]][coordinates[1]]
    
        def __delitem__(self, coordinates): 
            del self.array[coordinates[0]][coordinates[1]]
    
        def __setitem__(self, coordinates, number):
            self.check(coordinates[0], coordinates[1], number)
            self.array[coordinates[0]][coordinates[1]] = number
    
        def insert(self, coordinates, number):
            self.check(coordinates[0], coordinates[1], number)
            self.array[coordinates[0]].insert(coordinates[1], number)
    
        def __str__(self):
            return "\n".join(" ".join(map(str, row)) for row in self.array).replace("None", ".")
                             
        def get_row_values(self, row_number):
            return self.array[row_number][:]
    
        def get_column_values(self, column_number):
            return [row[column_number] for row in self.array]
    
        def get_nonet_values(self, row_number, column_number):
            width = isqrt(self.size)
            row_number -= row_number % width
            column_number -= column_number % width
            return [val for row in self.array[row_number:row_number+width]
                        for val in row[column_number:column_number+width]]
    

    Example use:

    sudoku = SudokuArray()
    sudoku[1, 2] = 9
    sudoku[2, 3] = 8
    sudoku[5, 5] = 4
    sudoku[4, 4] = 3
    sudoku[3, 3] = 6
    print(sudoku)