Давайте начнем с решения случая генерации n
или меньше строк в первую очередь. В этом случае ваш вывод должен быть латинским прямоугольником или латинским квадратом . Их легко создать: начните с построения латинского квадрата, перемешайте строки, перемешайте столбцы, а затем сохраните только первые r
строки. Следующее всегда работает для построения латинского квадрата для начала:
1 2 3 ... n
2 3 4 ... 1
3 4 5 ... 2
... ... ...
n 1 2 3 ...
Перестановка строк намного проще, чем перестановка столбцов, поэтому мы будем перемешивать строки, затем возьмем transpose затем снова перемешайте ряды. Вот реализация в Python:
from random import shuffle
def latin_rectangle(n, r):
square = [
[1 + (i + j) % n for i in range(n)]
for j in range(n)
]
shuffle(square)
square = list(zip(*square)) # transpose
shuffle(square)
return square[:r]
Пример:
>>> latin_rectangle(5, 4)
[(2, 4, 3, 5, 1),
(5, 2, 1, 3, 4),
(1, 3, 2, 4, 5),
(3, 5, 4, 1, 2)]
Обратите внимание, что этот алгоритм не может генерировать все возможные латинские квадраты; по построению строки представляют собой циклические c перестановки друг друга, поэтому вы не получите латинские квадраты в других классах эквивалентности . Я предполагаю, что это нормально, поскольку генерация равномерного распределения вероятностей по всем возможным выходным данным не является одним из требований вопроса.
Преимущество состоит в том, что это гарантированно сработает, и последовательно в O(n^2)
времени, потому что он не использует выборку отклонения или backtracking .
Теперь давайте рассмотрим случай, когда r > n
, то есть нам нужно больше строк. Каждый столбец не может иметь одинаковые частоты для каждого числа, кроме случаев, когда r % n == 0
, но это достаточно просто, чтобы гарантировать, что частоты в каждом столбце будут отличаться не более чем на 1. Сгенерируйте достаточно латинских квадратов, поместите их друг на друга, а затем отрежьте r
строки от него. Для дополнительной случайности можно перетасовать эти r
строки, но только после взятия фрагмента.
def generate_permutations(n, r):
rows = []
while len(rows) < r:
rows.extend(latin_rectangle(n, n))
rows = rows[:r]
shuffle(rows)
return rows
Пример:
>>> generate_permutations(5, 12)
[(4, 3, 5, 2, 1),
(3, 4, 1, 5, 2),
(3, 1, 2, 4, 5),
(5, 3, 4, 1, 2),
(5, 1, 3, 2, 4),
(2, 5, 1, 3, 4),
(1, 5, 2, 4, 3),
(5, 4, 1, 3, 2),
(3, 2, 4, 1, 5),
(2, 1, 3, 5, 4),
(4, 2, 3, 5, 1),
(1, 4, 5, 2, 3)]
При этом используются числа 1
до n
из-за формулы 1 + (i + j) % n
в первом понимании списка. Если вы хотите использовать что-то, кроме цифр от 1
до n
, вы можете взять это как список (например, players
) и изменить эту часть понимания списка на players[(i + j) % n]
, где n = len(players)
.