Вот векторизованное решение:
def tr(z):
return z*(z+1)//2
def snake(Y, X):
y, x = np.ogrid[:Y, :X]
mn, mx = np.minimum(X, Y), np.maximum(X, Y)
return (1 + tr(np.clip(x+y, None, mn))
+ mn * np.clip(x+y - mn, 0, None)
- tr(np.clip(x+y - mx, 0, None))
+ ((x+y) & 1) * (x - np.clip(x+y + 1 - Y, 0, None))
+ ((x+y + 1) & 1) * (y - np.clip(x+y + 1 - X, 0, None)))
Демо-версия:
>>> snake(7, 3)
array([[ 1, 3, 4],
[ 2, 5, 9],
[ 6, 8, 10],
[ 7, 11, 15],
[12, 14, 16],
[13, 17, 20],
[18, 19, 21]])
>>> snake(2, 4)
array([[1, 3, 4, 7],
[2, 5, 6, 8]])
объяснитель:
Функция tr
вычисляет количество элементов в треугольнике, которое больше или меньше половины квадрата (чуть больше, потому что мы включили диагональ). Это используется в snake
для вычисления смещения каждой диагонали; диагонали проиндексированы x+y
.
Точнее, первые три строки в операторе возврата вычисляют смещение по диагонали. Первая строка подсчитывает диагонали в верхнем левом треугольнике, вторая строка подсчитывает диагонали полной длины, а также в нижнем правом треугольнике; он также считает их полной длиной - третья строка исправляет это.
Последние две строки считаются в диагонали. Первый из двух в верхнем правом направлении, второй в нижнем левом направлении. Обратите внимание, что верхнее правое смещение равно координате x
для всех диагоналей, начинающихся с левого края. Поправочный член (np.clip ...
) предназначен для диагоналей, начинающихся с нижнего края. Аналогично, смещения в нижнем левом углу равны y
, если мы начинаем с верхнего края, и нуждаемся в коррекции, если мы начинаем с правого края.