2

I have 16 variables arranged in a 4 by 4 grid. My goal is to make a function that can assigns a random number between 0 and 4 to every variables, with no duplicates in each column and each row - like a sudoku.

Each method I've tried results in duplicates. For example:

column_A = [A1, A2, A3, A4]
column_B = [B1, B2, B3, B4]
column_C = [C1, C2, C3, C4]
column_D = [D1, D2, D3, D4]
row_1 = [A1, B1, C1, D1]
row_2 = [A2, B2, C2, D2]
row_3 = [A3, B3, C3, D3]
row_4 = [A4, B4, C4, D4]
all_rows = [row_1, row_2, row_3, row_4]
all_columns = [column_A, column_B, column_C, column_D]


def random_grid():
    for i in range(len(all_rows)):
      all_columns[i] = sample([0, 1, 2, 3, 4], 4)
      all_rows[i] = sample([0, 1, 2, 3, 4], 4)

doesn't work How could I do this?

3
  • I am happy to write you few different solution, but show my working code that indeed creates random 4x4 grid (even with duplicates) Commented Nov 1, 2022 at 23:20
  • I'd travel through the matrix in reading order (left to right, up to down) and fill each cell with a number randomly chosen from a list of available numbers ( 0, 1, 2, 3, 4 minus the ones already used to the left of your cell and above your cell). Commented Nov 1, 2022 at 23:25
  • Well indeed, this method can fail, which my code just proved to me :) Commented Nov 1, 2022 at 23:42

3 Answers 3

3

You just need to create a "baseline" grid, where no two rows or columns are the same. For example, for a grid of size 4x4, the baseline would be:

[[1, 2, 3, 4],
 [4, 1, 2, 3],
 [3, 4, 1, 2],
 [2, 3, 4, 1]]

Then shuffle the rows, then shuffle the columns.

Pure python:

random.shuffle only shuffles the elements of the list you passed. In order to shuffle columns, you will need to transpose the list-of-lists such that the first axis groups columns instead of rows.

import random

def random_grid(size=4):
    r = list(range(1, size+1))
    baseline = [r[-n:] + r[:-n] for n in range(size)]
    random.shuffle(baseline)
    transpose = [list(col) for col in zip(*baseline)]
    random.shuffle(transpose)
    return transpose

Using numpy:

np.random.shuffle only shuffles the first axis, so we transpose the array before shuffling it another time.

import numpy as np

def random_grid(size=4):
    r = list(range(1, size+1))
    baseline = np.array([r[-n:] + r[:-n] for n in range(size)])
    np.random.shuffle(baseline)
    np.random.shuffle(baseline.T)
    return baseline

Now, since you want random numbers between 0 and size, you can just get a grid that is one row and column larger than what you want, subtract one from every element, and discard the extra row and column.

Pure python:

def random_grid_2(size=4):
    rgrid = random_grid(size+1)
    return [[i - 1 for i in row[:-1]] for row in grid[:-1]]

Numpy:

def random_grid_2(size=4):
    rgrid = random_grid(size+1) - 1
    return rgrid[:-1, :-1]

Of course, this approach is less efficient than doing it in random_grid in the first place, when you are creating the grid, but I have kept the two steps separate to help you understand what is happening.

Sign up to request clarification or add additional context in comments.

3 Comments

This works nicely, but only uses numbers 1-4 and not 0-4, so it only provides a subset of all possible matrices.
@Swifty that's a good point that I totally overlooked. I've added a fix for that now.
Nice trick for your fix :)
1

Ok, here's some code that implements my method; more for demonstration than for practical use since, as was said in a comment, it can fail (if there are no choice left, which can happen for the 3 numbers in the bottom right corner of the matrix); this could be addressed by catching the error and restarting the process, but it's not very elegant.

from random import choice

numbers = {0,1,2,3,4}

a=[[],[],[],[]]

for i in range(4):
    for j in range(4):
        available = list(numbers - set(a[i][:j]) - {a[x][j] for x in range(i)})
        a[i].append(choice(available))
        
print(a)

Here's the amended code (with error catching); it will always work, but at the cost of potentially creating several failed matrices before a working one.

from random import choice

numbers = {0,1,2,3,4}

failed = True
while failed:
    a=[[],[],[],[]]
    failed = False
    try:
        for i in range(4):
            for j in range(4):
                available = list(numbers - set(a[i][:j]) - {a[x][j] for x in range(i)})
                a[i].append(choice(available))
    except:
        failed = True
        
print(a)

Comments

0

Pranav showed one great solution. A naive one (which should work in that case) could be generate any matrix and check if it's correct one.

Simple implementation:

from random import sample

def check(matrix, size=4):
    return check_rows(matrix, size) and check_cols(matrix, size)

def check_rows(matrix, size=4):
    return all(len(set(matrix[i])) == size for i in range(size))

def check_cols(matrix, size=4):
    transposed = [[matrix[j][i] for j in range(size)] for i in range(size)]
    return check_rows(transposed, size)

def generate(size=4):
    return [sample(range(5), size) for _ in range(size)]

while True:
    matrix = generate()
    if check(matrix):
        break

print(matrix)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.