How to generate a 2d array forming a chequerboard without numpy?

Question:

The problem is the following:

chequerboard(n, m, offset_h, offset_w), all arguments >=1, returns an n by m chequerboard where each (region) is of size offset_h by offset_w, the top left cell is always filled, and only the right hand and bottom edge may contain incomplete chequers.

Examples:

chequerboard(6, 8, 2, 2) returns
    [['#', '#', ' ', ' ', '#', '#', ' ', ' '],
     ['#', '#', ' ', ' ', '#', '#', ' ', ' '],
     [' ', ' ', '#', '#', ' ', ' ', '#', '#'],
     [' ', ' ', '#', '#', ' ', ' ', '#', '#'],
     ['#', '#', ' ', ' ', '#', '#', ' ', ' '],
     ['#', '#', ' ', ' ', '#', '#', ' ', ' ']]

chequerboard(5, 7, 2, 3) returns
    [['#', '#', '#', ' ', ' ', ' ', '#'],
     ['#', '#', '#', ' ', ' ', ' ', '#'],
     [' ', ' ', ' ', '#', '#', '#', ' '],
     [' ', ' ', ' ', '#', '#', '#', ' '],
     ['#', '#', '#', ' ', ' ', ' ', '#']]

How can I solve it without Numpy? i.e., using only vanilla python?

Asked By: Leone

||

Answers:

You could utilize the modulo operator (%):

def print_chequerboard(board: list[list[str]]) -> None:
    for row in board:
        print(row)


def chequerboard(n: int, m: int, offset_h: int, offset_w: int) -> list[list[str]]:
    board = [[' ' for _ in range(m)] for _ in range(n)]
    for i in range(n):
        for j in range(m):
            if i // offset_h % 2 == 0 and j // offset_w % 2 == 0 or 
               i // offset_h % 2 == 1 and j // offset_w % 2 == 1:
                board[i][j] = '#'
    return board


def main() -> None:
    print('chequerboard(6, 8, 2, 2): ')
    print_chequerboard(chequerboard(6, 8, 2, 2))
    print('chequerboard(5, 7, 2, 3): ')
    print_chequerboard(chequerboard(5, 7, 2, 3))
    print('chequerboard(6, 8, 2, 2): ')
    print_chequerboard(chequerboard(6, 8, 2, 2))
    print('chequerboard(5, 7, 2, 3): ')
    print_chequerboard(chequerboard(5, 7, 2, 3))


if __name__ == '__main__':
    main()

Output:

chequerboard(6, 8, 2, 2): 
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
chequerboard(5, 7, 2, 3): 
['#', '#', '#', ' ', ' ', ' ', '#']
['#', '#', '#', ' ', ' ', ' ', '#']
[' ', ' ', ' ', '#', '#', '#', ' ']
[' ', ' ', ' ', '#', '#', '#', ' ']
['#', '#', '#', ' ', ' ', ' ', '#']
chequerboard(6, 8, 2, 2): 
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
chequerboard(5, 7, 2, 3): 
['#', '#', '#', ' ', ' ', ' ', '#']
['#', '#', '#', ' ', ' ', ' ', '#']
[' ', ' ', ' ', '#', '#', '#', ' ']
[' ', ' ', ' ', '#', '#', '#', ' ']
['#', '#', '#', ' ', ' ', ' ', '#']
Answered By: Sash Sinha

You can do it by creating the first row and then repeating it offset_h times. This is then rolled by offset_w for the subsequent offset_h rows. Each row is sliced to the desired size and the process is repeated.

def ceiling(a, b):
    return -(a//-b)

def chequerboard(n, m, offset_h, offset_w):
    pattern = ['#']*offset_w+[' ']*offset_w
    row = pattern + pattern * (ceiling(m, 2*offset_w)-1)
    return [(row[offset_w:]+row[:offset_w])[:m] if i % 2 else row[:m] for i in range(ceiling(n, offset_h)) for _ in range(offset_h)][:n]

Test:

for i in ((6, 8, 2, 2), (5, 7, 2, 3)):
    for j in chequerboard(*i):
        print(j)
    print('n')

Output:

['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
[' ', ' ', '#', '#', ' ', ' ', '#', '#']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']
['#', '#', ' ', ' ', '#', '#', ' ', ' ']


['#', '#', '#', ' ', ' ', ' ', '#']
['#', '#', '#', ' ', ' ', ' ', '#']
[' ', ' ', ' ', '#', '#', '#', ' ']
[' ', ' ', ' ', '#', '#', '#', ' ']
['#', '#', '#', ' ', ' ', ' ', '#']

Benchmarks:

assert chequerboard(6, 8, 2, 2) == chequerboard_sash(6, 8, 2, 2)

%timeit chequerboard(6, 8, 2, 2)
%timeit chequerboard_sash(6, 8, 2, 2)

Output:

2.1 µs ± 30.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
10.3 µs ± 74.7 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
Answered By: Nin17