例如,如果有5个数字1,2,3,4,5
我想要一个像
这样的随机结果[[ 2, 3, 1, 4, 5]
[ 5, 1, 2, 3, 4]
[ 3, 2, 4, 5, 1]
[ 1, 4, 5, 2, 3]
[ 4, 5, 3, 1, 2]]
确保其行和列中的每个数字都是唯一的。
有一种有效的方法吗?
我尝试使用while循环为每次迭代生成一行,但似乎效率不高。
import numpy as np
numbers = list(range(1,6))
result = np.zeros((5,5), dtype='int32')
row_index = 0
while row_index < 5:
np.random.shuffle(numbers)
for column_index, number in enumerate(numbers):
if number in result[:, column_index]:
break
else:
result[row_index, :] = numbers
row_index += 1
答案 0 :(得分:4)
仅为了您的信息,您正在寻找的是一种生成latin squares的方法。 至于解决方案,它取决于随机“随机”对你有多少。
我会设计至少四种主要技术,其中两项已经提出。 因此,我将简要介绍另外两个:
假设我们使用标准的Python数据类型,因为我没有看到使用NumPy的真正优点(但如果需要,结果可以很容易地转换为np.ndarray
),这将在代码中(第一个函数只是检查解决方案是否真的正确):
import random
import math
import itertools
# this only works for Iterable[Iterable]
def is_latin_rectangle(rows):
valid = True
for row in rows:
if len(set(row)) < len(row):
valid = False
if valid and rows:
for i, val in enumerate(rows[0]):
col = [row[i] for row in rows]
if len(set(col)) < len(col):
valid = False
break
return valid
def is_latin_square(rows):
return is_latin_rectangle(rows) and len(rows) == len(rows[0])
# : prepare the input
n = 9
items = list(range(1, n + 1))
# shuffle items
random.shuffle(items)
# number of permutations
print(math.factorial(n))
def latin_square1(items, shuffle=True):
result = []
for elems in itertools.permutations(items):
valid = True
for i, elem in enumerate(elems):
orthogonals = [x[i] for x in result] + [elem]
if len(set(orthogonals)) < len(orthogonals):
valid = False
break
if valid:
result.append(elems)
if shuffle:
random.shuffle(result)
return result
rows1 = latin_square1(items)
for row in rows1:
print(row)
print(is_latin_square(rows1))
def latin_square2(items, shuffle=True, forward=False):
sign = -1 if forward else 1
result = [items[sign * i:] + items[:sign * i] for i in range(len(items))]
if shuffle:
random.shuffle(result)
return result
rows2 = latin_square2(items)
for row in rows2:
print(row)
print(is_latin_square(rows2))
rows2b = latin_square2(items, False)
for row in rows2b:
print(row)
print(is_latin_square(rows2b))
为了进行比较,还提出了通过尝试随机排列和接受有效排列(基本上是@hpaulj提出的建议)的实现。
def latin_square3(items):
result = [list(items)]
while len(result) < len(items):
new_row = list(items)
random.shuffle(new_row)
result.append(new_row)
if not is_latin_rectangle(result):
result = result[:-1]
return result
rows3 = latin_square3(items)
for row in rows3:
print(row)
print(is_latin_square(rows3))
我没有时间(还)实现其他方法(从@ConfusedByCode回溯类似Sudoku的解决方案)。
有n = 5
的时间安排:
%timeit latin_square1(items)
321 µs ± 24.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit latin_square2(items)
7.5 µs ± 222 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit latin_square2(items, False)
2.21 µs ± 69.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit latin_square3(items)
2.15 ms ± 102 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
...以及n = 9
:
%timeit latin_square1(items)
895 ms ± 18.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit latin_square2(items)
12.5 µs ± 200 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit latin_square2(items, False)
3.55 µs ± 55.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit latin_square3(items)
The slowest run took 36.54 times longer than the fastest. This could mean that an intermediate result is being cached.
9.76 s ± 9.23 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
因此,解决方案1给出了相当大的随机性,但速度并不快(并且使用O(n!)
缩放),解决方案2(和2b)要快得多(使用O(n)
缩放)但是不像解决方案1那样随机。解决方案3非常慢并且性能可能会有很大差异(可能通过计算最后一次迭代而不是猜测来加速)。
在下面讨论了更多技术性的,其他有效的算法:
答案 1 :(得分:2)
这可能看起来很奇怪,但你基本上已经描述了生成一个随机的n维数独谜题。来自a blog post by Daniel Beer:
解决数独谜题的基本方法是通过回溯搜索每个细胞的候选值。一般程序如下:
为每个单元格生成候选值列表,方法是从所有可能值的集合开始,并消除与正在检查的单元格出现在同一行,列和框中的值。
< / LI>选择一个空单元格。如果没有,拼图就解决了。
如果单元格没有候选值,则难题无法解决。
- 醇>
对于该单元格中的每个候选值,将值放在单元格中并尝试以递归方式解决难题。
有两种优化可以大大提高此算法的性能:
选择单元格时,请始终选择候选值最小的单元格。这降低了分支因子。随着值添加到网格中,其他单元格的候选数量也会减少。
- 醇>
在分析空单元格的候选值时,从上一步骤的分析开始并通过删除最后修改单元格的行,列和框中的值来更快地修改它。这是拼图大小的O(N),而从头开始分析是O(N3)。
在您的情况下,“无法解决的难题”是无效的矩阵。在可解决的难题中,矩阵中的每个元素在两个轴上都是唯一的。
答案 2 :(得分:1)
编辑:以下是norok2答案中第二个解决方案的实现。
编辑:我们可以再次对生成的方块进行洗牌,使其真正随机。 所以求解函数可以修改为:
def solve(numbers):
shuffle(numbers)
shift = randint(1, len(numbers)-1)
res = []
for r in xrange(len(numbers)):
res.append(list(numbers))
numbers = list(numbers[shift:] + numbers[0:shift])
rows = range(len(numbers))
shuffle(rows)
shuffled_res = []
for i in xrange(len(rows)):
shuffled_res.append(res[rows[i]])
return shuffled_res
编辑:我以前误解了这个问题。
所以,这里有一个快速的&#39;产生“某种程度”的方法。随机解决方案
基本的想法是,
a, b, c
b, c, a
c, a, b
我们可以通过固定步骤移动一行数据以形成下一行。这符合我们的限制。
所以,这是代码:
from random import shuffle, randint
def solve(numbers):
shuffle(numbers)
shift = randint(1, len(numbers)-1)
res = []
for r in xrange(len(numbers)):
res.append(list(numbers))
numbers = list(numbers[shift:] + numbers[0:shift])
return res
def check(arr):
for c in xrange(len(arr)):
col = [arr[r][c] for r in xrange(len(arr))]
if len(set(col)) != len(col):
return False
return True
if __name__ == '__main__':
from pprint import pprint
res = solve(range(5))
pprint(res)
print check(res)
这是itertools可能的解决方案,如果你不坚持使用我不熟悉的numpy:
import itertools
from random import randint
list(itertools.permutations(range(1, 6)))[randint(0, len(range(1, 6))]
# itertools returns a iterator of all possible permutations of the given list.
答案 3 :(得分:1)
我尝试了一种蛮力的随机选择。生成一行,如果有效,则添加到累计行:
def foo(n=5,maxi=200):
arr = np.random.choice(numbers,n, replace=False)[None,:]
for i in range(maxi):
row = np.random.choice(numbers,n, replace=False)[None,:]
if (arr==row).any(): continue
arr = np.concatenate((arr, row),axis=0)
if arr.shape[0]==n: break
print(i)
return arr
一些示例运行:
In [66]: print(foo())
199
[[1 5 4 2 3]
[4 1 5 3 2]
[5 3 2 1 4]
[2 4 3 5 1]]
In [67]: print(foo())
100
[[4 2 3 1 5]
[1 4 5 3 2]
[5 1 2 4 3]
[3 5 1 2 4]
[2 3 4 5 1]]
In [68]: print(foo())
57
[[1 4 5 3 2]
[2 1 3 4 5]
[3 5 4 2 1]
[5 3 2 1 4]
[4 2 1 5 3]]
In [69]: print(foo())
174
[[2 1 5 4 3]
[3 4 1 2 5]
[1 3 2 5 4]
[4 5 3 1 2]
[5 2 4 3 1]]
In [76]: print(foo())
41
[[3 4 5 1 2]
[1 5 2 3 4]
[5 2 3 4 1]
[2 1 4 5 3]
[4 3 1 2 5]]
所需的尝试次数在各地都有所不同,有些超过了我的迭代限制。
在没有进入任何理论的情况下,快速生成2d排列与生成某种意义上的最大排列或最大随机排序之间存在差异。我怀疑我的方法更接近这个随机目标,而不是更系统和有效的方法(但我无法证明)。
def opFoo():
numbers = list(range(1,6))
result = np.zeros((5,5), dtype='int32')
row_index = 0; i = 0
while row_index < 5:
np.random.shuffle(numbers)
for column_index, number in enumerate(numbers):
if number in result[:, column_index]:
break
else:
result[row_index, :] = numbers
row_index += 1
i += 1
return i, result
In [125]: opFoo()
Out[125]:
(11, array([[2, 3, 1, 5, 4],
[4, 5, 1, 2, 3],
[3, 1, 2, 4, 5],
[1, 3, 5, 4, 2],
[5, 3, 4, 2, 1]]))
我的速度比OP快一点,但我的是正确的。
这是对我的改进(快2倍):
def foo1(n=5,maxi=300):
numbers = np.arange(1,n+1)
np.random.shuffle(numbers)
arr = numbers.copy()[None,:]
for i in range(maxi):
np.random.shuffle(numbers)
if (arr==numbers).any(): continue
arr = np.concatenate((arr, numbers[None,:]),axis=0)
if arr.shape[0]==n: break
return arr, i
Why is translated Sudoku solver slower than original?
我发现使用Java Sudoku解算器的这个翻译,使用Python列表比numpy数组更快。
我可能会尝试让这个脚本适应这个问题 - 明天。
答案 4 :(得分:0)
无法从手机输入代码,这是伪代码:
创建一个矩阵,其中一个diamention超过tge目标矩阵(3 d)
使用1到5的数字初始化25个元素
迭代25个元素。
从元素列表中选择第一个元素的随机值(包含数字1到5)
从行和列中的所有元素中删除随机选择的值。
对所有元素重复步骤4和5。