Как прокомментировал Пол Панцер, можно построить обычный шахматный шаблон:
def uniq(shape):
f = lambda *idx: np.mod(np.sum(idx, axis=0), 10)
return np.fromfunction(f, shape)
Например, uniq((5, 17))
равно
[[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0. 1. 2. 3. 4. 5. 6.]
[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 0. 1. 2. 3. 4. 5. 6. 7.]
[ 2. 3. 4. 5. 6. 7. 8. 9. 0. 1. 2. 3. 4. 5. 6. 7. 8.]
[ 3. 4. 5. 6. 7. 8. 9. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
[ 4. 5. 6. 7. 8. 9. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0.]]
Нет смежных элементов равных.По модулю может быть даже взят мод 2, в результате чего получается массив 0-1.
Если вы хотите, чтобы массив был (или хотя бы выглядел) случайным образом, могут помочь дробные шаги.
def uniq(shape):
steps = 1 + np.mod(np.random.randint(1, 100, size=len(shape))*(np.sqrt(5)+1)/2, 8)
f = lambda *idx: np.mod(np.floor(np.random.uniform(0, 10) + np.moveaxis(idx, 0, -1).dot(steps)), 10)
return np.fromfunction(f, shape)
Теперь uniq((5, 17))
- это нечто случайное, похожее на
[[ 4. 8. 3. 7. 2. 7. 1. 6. 0. 5. 0. 4. 9. 3. 8. 3. 7.]
[ 0. 4. 9. 4. 8. 3. 7. 2. 7. 1. 6. 0. 5. 0. 4. 9. 3.]
[ 6. 1. 5. 0. 4. 9. 4. 8. 3. 7. 2. 7. 1. 6. 0. 5. 0.]
[ 2. 7. 1. 6. 1. 5. 0. 4. 9. 4. 8. 3. 7. 2. 7. 1. 6.]
[ 8. 3. 8. 2. 7. 1. 6. 1. 5. 0. 4. 9. 4. 8. 3. 7. 2.]]
В первой версии все шаги равны 1. Вычисление шагов основано на золотом сечении (np.sqrt(5)+1)/2
потому что его множители производят равномерно распределенные, но случайные числа.Шаги гарантированно будут между 1 и 9, поэтому с каждым шагом, после настила и принятия мода 10, мы уверены, что у нас будет другое число.