Найдите все 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 lines
    • x axis (0, 1, 2)
    • ось у (0, 3, 6)
    • ось Z (0, 9, 18)
  • 2D diagonals
    • x/y axes (0, 4, 8, 2, 4, 6)
    • оси x/z (0, 10, 20, 2, 10, 18)
    • оси y/z (0, 12, 24, 6, 12, 18)
  • 3D diagonals
    • x/y/z axes (0, 13, 26, 2, 13, 24)

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


person 2Cubed    schedule 27.08.2016    source источник
comment
Каково значение двух выделенных блоков для диагоналей 2D? На центральной диагонали будет 3 элемента; вы включаете антидиагональ в тот же кортеж, но каким-то образом разделены. Вас также интересуют офсетные диагонали?   -  person hpaulj    schedule 27.08.2016
comment
Вы хотите сгенерировать (0, 1, 2) и (2, 1, 0)?   -  person Eric    schedule 27.08.2016
comment
Вы пытаетесь реализовать это?   -  person Eric    schedule 27.08.2016
comment
@hpaulj Они должны продемонстрировать, что меня интересуют диагонали, идущие в обоих направлениях.   -  person 2Cubed    schedule 28.08.2016
comment
@ Эрик Я не хочу генерировать и (0, 1, 2), и (2, 1, 0) - только одно или другое. Концептуально логика этой игры — это то, что я хотел бы реализовать, но в обобщенном формате (для любого размера и количества измерений).   -  person 2Cubed    schedule 28.08.2016
comment
Эта игра обобщает k, но не n. Мой ответ здесь обобщает оба   -  person Eric    schedule 28.08.2016


Ответы (3)


Это решение обобщено для 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)*$ (используя регулярное выражение для ее суммирования). Это потому что:

  • должен быть хотя бы один fi, чтобы «линия» не была точкой, выбранной повторно
  • никакие bis не стоят перед fis, чтобы избежать перевернутых строк

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), который дает массив, в котором каждое значение является собственным индексом.


Я уже решал эту проблему, чтобы реализовать крестики-нолики более высокого уровня.

person Eric    schedule 27.08.2016

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]])
person hpaulj    schedule 27.08.2016
comment
Эти функции не получают все последовательности для каждого случая. Например, x[0,0,:] извлекает только первую строку, тогда как она должна получить [0, 1, 2], [3, 4, 5], ..., [21, 22, 23], [24, 25, 26]. Последовательности в скобках в моем вопросе - это просто примеры желаемого результата. - person 2Cubed; 27.08.2016
comment
Я говорил об итерации по различным индексам, необходимым для получения всех срезов. - person hpaulj; 27.08.2016

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

# 3d diagonals
print(np.einsum('iii->i', a))
# 2d diagonals
print(np.einsum('iij->ij', a))
print(np.einsum('iji->ij', a))
person Eelco Hoogendoorn    schedule 27.08.2016
comment
Но это суммирование, а не просто нарезка - person Eric; 28.08.2016
comment
он суммирует только индексы, которые появляются слева, но не справа. - person Eelco Hoogendoorn; 28.08.2016