Найти все n-мерные линии и диагонали с NumPy

Используя NumPy, я хотел бы создать список всех линий и диагоналей n-мерного массива с длиной k.


Возьмем случай следующего трехмерного массива с длинами трех.

array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23],
        [24, 25, 26]]])

В этом случае я хотел бы получить все следующие типы последовательностей. Для любого данного случая я хотел бы получить все возможные последовательности каждого типа. Примеры желаемых последовательностей приведены в скобках ниже, для каждого случая.

  • 1D строки
    • ось x (0, 1, 2)
    • y ось (0, 3, 6)
    • ось z (0, 9, 18)
  • Двумерные диагонали
    • x/y оси (0, 4, 8, 2, 4, 6)
    • x/z оси (0, 10, 20, 2, 10, 18)
    • оси y/z (0, 12, 24, 6, 12, 18)
  • Трехмерные диагонали
    • x/y/z оси (0, 13, 26, 2, 13, 24)

Решение должно быть обобщено, так что оно будет генерировать все строки и диагонали для массива, независимо от количества массивов измерений или длины (которое является постоянным во всех измерениях).

Ответ 1

Это решение обобщено на n

Позволяет перефразировать эту проблему как "найти список индексов".

Мы ищем все 2d массивы индексов формы

array[i[0], i[1], i[2], ..., i[n-1]]

Пусть n = arr.ndim

Где i - массив формы (n, k)

Каждый из i[j] может быть одним из:

  • Тот же индекс повторяется n раз, ri[j] = [j, ..., j]
  • Последующая последовательность, fi = [0, 1, ..., k-1]
  • Обратная последовательность, bi = [k-1, ..., 1, 0]

С требованиями, что каждая последовательность имеет вид ^(ri)*(fi)(fi|bi|ri)*$ (с использованием regex для ее суммирования). Это происходит потому, что:

  • должен быть хотя бы один fi, поэтому "строка" не является точкой, выбранной повторно
  • no bi перед fi s, чтобы избежать обратных строк

def product_slices(n):
    for i in range(n):
        yield (
            np.index_exp[np.newaxis] * i +
            np.index_exp[:] +
            np.index_exp[np.newaxis] * (n - i - 1)
        )

def get_lines(n, k):
    """
    Returns:
        index (tuple):   an object suitable for advanced indexing to get all possible lines
        mask (ndarray):  a boolean mask to apply to the result of the above
    """
    fi = np.arange(k)
    bi = fi[::-1]
    ri = fi[:,None].repeat(k, axis=1)

    all_i = np.concatenate((fi[None], bi[None], ri), axis=0)

    # inedx which look up every possible line, some of which are not valid
    index = tuple(all_i[s] for s in product_slices(n))

    # We incrementally allow lines that start with some number of `ri`s, and an `fi`
    #  [0]  here means we chose fi for that index
    #  [2:] here means we chose an ri for that index
    mask = np.zeros((all_i.shape[0],)*n, dtype=np.bool)
    sl = np.index_exp[0]
    for i in range(n):
        mask[sl] = True
        sl = np.index_exp[2:] + sl

    return index, mask

Применяется к вашему примеру:

# construct your example array
n = 3
k = 3
data = np.arange(k**n).reshape((k,)*n)

# apply my index_creating function
index, mask = get_lines(n, k)

# apply the index to your array
lines = data[index][mask]
print(lines)
array([[ 0, 13, 26],
       [ 2, 13, 24],
       [ 0, 12, 24],
       [ 1, 13, 25],
       [ 2, 14, 26],
       [ 6, 13, 20],
       [ 8, 13, 18],
       [ 6, 12, 18],
       [ 7, 13, 19],
       [ 8, 14, 20],
       [ 0, 10, 20],
       [ 2, 10, 18],
       [ 0,  9, 18],
       [ 1, 10, 19],
       [ 2, 11, 20],
       [ 3, 13, 23],
       [ 5, 13, 21],
       [ 3, 12, 21],
       [ 4, 13, 22],
       [ 5, 14, 23],
       [ 6, 16, 26],
       [ 8, 16, 24],
       [ 6, 15, 24],
       [ 7, 16, 25],
       [ 8, 17, 26],
       [ 0,  4,  8],
       [ 2,  4,  6],
       [ 0,  3,  6],
       [ 1,  4,  7],
       [ 2,  5,  8],
       [ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 13, 17],
       [11, 13, 15],
       [ 9, 12, 15],
       [10, 13, 16],
       [11, 14, 17],
       [ 9, 10, 11],
       [12, 13, 14],
       [15, 16, 17],
       [18, 22, 26],
       [20, 22, 24],
       [18, 21, 24],
       [19, 22, 25],
       [20, 23, 26],
       [18, 19, 20],
       [21, 22, 23],
       [24, 25, 26]])

Другим хорошим набором тестовых данных является np.moveaxis(np.indices((k,)*n), 0, -1), который дает массив, где каждое значение является его собственным индексом


Я решил эту проблему прежде, чем реализовать более высокий размер tic-tac-toc

Ответ 2

In [1]: x=np.arange(27).reshape(3,3,3)

Выбор индивидуального rows прост:

In [2]: x[0,0,:]
Out[2]: array([0, 1, 2])
In [3]: x[0,:,0]
Out[3]: array([0, 3, 6])
In [4]: x[:,0,0]
Out[4]: array([ 0,  9, 18])

Вы можете перебирать размеры с помощью списка индексов:

In [10]: idx=[slice(None),0,0]
In [11]: x[idx]
Out[11]: array([ 0,  9, 18])
In [12]: idx[2]+=1
In [13]: x[idx]
Out[13]: array([ 1, 10, 19])

Посмотрите на код np.apply_along_axis, чтобы узнать, как он реализует такую ​​итерацию.

Изменить форму и разделить можно также создать список rows. Для некоторых измерений для этого может потребоваться transpose:

In [20]: np.split(x.reshape(x.shape[0],-1),9,axis=1)
Out[20]: 
[array([[ 0],
        [ 9],
        [18]]), array([[ 1],
        [10],
        [19]]), array([[ 2],
        [11],
        ...

np.diag может получать диагонали от 2d-подмассивов

In [21]: np.diag(x[0,:,:])
Out[21]: array([0, 4, 8])
In [22]: np.diag(x[1,:,:])
Out[22]: array([ 9, 13, 17])
In [23]: np.diag?
In [24]: np.diag(x[1,:,:],1)
Out[24]: array([10, 14])
In [25]: np.diag(x[1,:,:],-1)
Out[25]: array([12, 16])

И исследуйте np.diagonal для прямого применения к 3d. Также легко индексировать массив напрямую, range и arange, x[0,range(3),range(3)].

Насколько я знаю, нет возможности перешагнуть все эти альтернативы. Поскольку размеры возвращаемых массивов могут различаться, мало смысла создавать такую ​​функцию в скомпилированном numpy-коде. Таким образом, даже если бы была функция, она выполнила бы альтернативы, как я изложил.

==============

Все 1d строки

x.reshape(-1,3)
x.transpose(0,2,1).reshape(-1,3)
x.transpose(1,2,0).reshape(-1,3)

y/z диагональная и антидиагональная

In [154]: i=np.arange(3)
In [155]: j=np.arange(2,-1,-1)
In [156]: np.concatenate((x[:,i,i],x[:,i,j]),axis=1)
Out[156]: 
array([[ 0,  4,  8,  2,  4,  6],
       [ 9, 13, 17, 11, 13, 15],
       [18, 22, 26, 20, 22, 24]])

Ответ 3

np.einsum может использоваться для построения всех этих выражений; например:

# 3d diagonals
print(np.einsum('iii->i', a))
# 2d diagonals
print(np.einsum('iij->ij', a))
print(np.einsum('iji->ij', a))