How to generate all possibilities of sudoku-like grids in Python?

Question:

I need to generate all the possibilities of sudoku-like grids/matrices. A list of 2D arrays must be returned.

Note: What I am trying to generate is not really a 9×9 sudoku grid but something like the grid below, where the only condition is that there are no repeated numbers in the rows and columns:

1 2 3
2 3 1
3 1 2

So, for example,

>> generate(3)
[[[1,2,3],[2,3,1],[3,1,2]], [[1,2,3],[3,1,2],[2,3,1]], [[1,3,2],[2,1,3],[3,2,1]], [[1,3,2],[3,2,1],[2,1,3]], ...]
>> generate(4)
[[[1,2,3,4],[2,1,4,3],[3,4,1,2],[4,3,2,1]], ...]

Any help would be appreciated 🙂

Asked By: J Muzhen

||

Answers:

I think you’re looking for something like this –

  • generate_base returns a "base grid" of rows where numbers 1…N are "shifted" for N rows (so e.g. 1,2,3, 2,3,1, 3,1,2)
  • generate calls that function and itertools.permutations() to generate the possible "vertical" permutations of the base grid (i.e. it shuffles the rows around)
import itertools


def generate_base(n):
    return [tuple(((i + x) % n) + 1 for x in range(n)) for i in range(n)]


def generate(n):
    return itertools.permutations(generate_base(n))


for grid in generate(4):
    print(grid)

The output for 4 is e.g.

((1, 2, 3, 4), (2, 3, 4, 1), (3, 4, 1, 2), (4, 1, 2, 3))
((1, 2, 3, 4), (2, 3, 4, 1), (4, 1, 2, 3), (3, 4, 1, 2))
((1, 2, 3, 4), (3, 4, 1, 2), (2, 3, 4, 1), (4, 1, 2, 3))
((1, 2, 3, 4), (3, 4, 1, 2), (4, 1, 2, 3), (2, 3, 4, 1))
((1, 2, 3, 4), (4, 1, 2, 3), (2, 3, 4, 1), (3, 4, 1, 2))
...
Answered By: AKX

I think I found a solution, is this what you’re looking for?

def generate(n):
        #combinations :
        liste = list(itertools. product( list(range(1,n+1)), repeat=n)) 
        liste = list(map(list, liste))
        #combinations of the combinations :
        result =  list(itertools. product(liste, repeat=n))
        return (list(map(list, result)))
Answered By: grymlin

Here is a naive algorithm that finds all the different possible sudoku boards of a given size. It works by iterating through the Cartesian product of the permutations of the digits with as many repetitions as there are rows, and then discarding any that have columns with any repetition of digits.

import itertools

def are_columns_unique(board):
    columns = [set() for _ in range(len(board))]
    for row in board:
        for i, value in enumerate(row):
            column = columns[i]
            if value in column:
                return False
            else:
                column.add(value)
    return True

def generate_boards(n):
    digits = tuple(range(1, n + 1))
    for board in itertools.product(itertools.permutations(digits), repeat=n):
        if are_columns_unique(board):
            yield board

def format_board(board):
    return "n".join("".join(map(str, row)) for row in board)

for board in generate_boards(3):
    print(format_board(board))
    print()

This outputs the following:

123
231
312

123
312
231

132
213
321

...

It should be possible to make a more efficient algorithm by detecting duplicate columns while still generating the board, but this is a start.


Edit on 2022-09-10: I wrote a more efficient algorithm. This one can print all sudoku boards of length 5 in a couple of minutes, whereas the naive algorithm above would churn for ages before printing even a single board.

"""
usage: python sudoku.py [-h] n

Print all sudoku-like boards of a given size

positional arguments:
  n           The size of the sudoku-like board

optional arguments:
  -h, --help  show this help message and exit
"""

import argparse
import itertools


def dont_skip(current_result):
    return -1


def product(*args, repeat=1, skip=dont_skip):
    """Find the Cartesian product of the arguments.

    This is the same as itertools.product, with the addition of the skip
    argument.

    The skip argument is a function to specify whether the current result
    should be skipped, and if so, which iterator the next value should come
    from. It should be a function that takes the result of one iteration, and
    returns the index of the iterator to skip, or -1 if the result should not
    be skipped.

    Iterators earlier in the argument list have higher priority than iterators
    later in the argument list; skipping a value from an earlier iterator will
    skip all products of later iterators that would have included that value.
    """
    # Initialize data structures and handle bad input
    if len(args) == 0:
        return []
    gears = [tuple(arg) for arg in args] * repeat
    for gear in gears:
        if len(gear) == 0:
            return []
    tooth_numbers = [0] * len(gears)
    result = [gear[0] for gear in gears]

    # Rotate through all gears
    finished = False
    while not finished:
        current_result = tuple(result)
        gear_to_skip = skip(current_result)
        if gear_to_skip >= 0:
            gear_number = gear_to_skip
        else:
            yield current_result
            gear_number = len(gears) - 1

        # Get next result
        while gear_number >= 0:
            gear = gears[gear_number]
            tooth_number = tooth_numbers[gear_number] + 1
            if tooth_number < len(gear):
                # No gear change is necessary, so exit the loop
                result[gear_number] = gear[tooth_number]
                tooth_numbers[gear_number] = tooth_number
                break
            result[gear_number] = gear[0]
            tooth_numbers[gear_number] = 0
            gear_number -= 1
        else:
            # We changed all the gears, so we are back at the beginning
            finished = True


def skip_duplicate_columns(board):
    columns = [set() for _ in range(len(board))]
    for i, row in enumerate(board):
        for j, value in enumerate(row):
            column = columns[j]
            if value in column:
                return i
            else:
                column.add(value)
    return -1


def generate_boards(n):
    digits = tuple(range(1, n + 1))
    for board in product(
        itertools.permutations(digits), repeat=n, skip=skip_duplicate_columns
    ):
        yield board


def format_board(board):
    return "n".join("".join(map(str, row)) for row in board)


def parse_args():
    parser = argparse.ArgumentParser(
        description="Print all sudoku-like boards of a given size"
    )
    parser.add_argument("n", type=int, help="The size of the sudoku-like board")
    return parser.parse_args()


def main():
    args = parse_args()
    for board in generate_boards(args.n):
        print(format_board(board))
        print()


if __name__ == "__main__":
    main()

This uses my own code for generating the Cartesian product of the arguments, instead of itertools.product. My code is based on the algorithm from PyPy, which is released under the MIT licence. My adapted code introduces a skip argument, which allows callers to skip unnecessary iterations. This reduces the number of necessary calculations considerably.

Answered By: Jack Taylor
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.