О форме-агностическом разрезе ndarrays

В этом сообщении я использую термин slice для обозначения подмассива B_i n-мерного массива A, для которого size(B_i, d) равно 1 для некоторой размерности d. A состоит из size(A, d) таких срезов, объединенных вдоль размерности d.

Например, если ndims(A) равно 6, а d равно 3, то выражения вида

A(:, :, i, :, :, :)

для i в 1:size(A, d) представляют все срезы (по размеру d), составляющие A.

Проблема с выражением типа A(:, :, i, :, :, :) заключается в том, что она не может быть символически обобщена на срезы по размеру, отличному от 3 в массивах, имеющих ряд измерений, отличных от 6. Например, чтобы получить A срезы вдоль размерности 2, нужно было бы другое выражение, A(:, i, :, :, :, :). Это означает, что такие выражения бесполезны в коде, который агностик относительно формы некоторого массива, из которого нужно извлечь фрагменты.

Ниже приведена моя попытка использования Matlab-noob для реализации аппликации фигур-агностиков. (Имя slice уже выполнено, поэтому я назвал функцию hslice, сокращенную для hyperslice.) Стратегия функции состоит в том, чтобы преобразовать входной массив в подходящий 3-мерный массив, взять желаемый фрагмент вдоль измененного массива второго размера и изменить форму результата, чтобы иметь форму среза из исходного входного массива.

function out = hslice(ndarray, d, i)
    sz = size(ndarray);
    pfx = sz(1:d-1);    % dimensions before d
    sfx = sz(d+1:end);  % dimensions after d

    tmp = reshape(ndarray, prod(pfx), sz(d), prod(sfx));
    out = reshape(tmp(:, i, :), [pfx 1 sfx]);
end

Есть ли встроенный или, по крайней мере, более эффективный способ достижения одного и того же результата (в форме-агностическом)?

Ответы

Ответ 1

Да. Вы можете использовать эквивалентность между разделяемыми ячейками ячеек и "списками, разделенными запятыми", и тем фактом, что вы можете использовать char ':' в качестве индекса для динамического создания этого вызова A(:, :, :, i, :, ...) для произвольных измерений.

function out = slice(A, ix, dim)

subses = repmat({':'}, [1 ndims(A)]);
subses{dim} = ix;
out = A(subses{:});

Это будет полностью обобщено и будет выполнять точно такую ​​же операцию индексации "среза", что и исходное статическое выражение A(:, :, i, :, ...), кроме накладных расходов, чтобы свернуть эти строки, чтобы настроить его.

Или, если вы этого захотите, вы можете просто использовать sprintf для создания этого A(:, :, i, :, ...) в качестве строки, а затем вызвать eval() на нем. Но мне нравится избегать eval, если это вообще возможно.

Обратите внимание, что ваша оригинальная реализация использует быстрые операции и должна выполняться просто отлично, примерно так же быстро, как эта. Я просто размещаю это, потому что я считаю, что он очень читабельен, отвечает на ваш вопрос как первоначально поставленный, и он может быть применен к другим полезным материалам.

Назначение срезам

Вы также можете использовать тот же метод индексов в ячейках, что и lvalue для назначения в срезах массива. Однако вы не можете повторно использовать функцию среза, так как он возвращает извлеченное подмножество массива, а не ссылку на lvalue. Таким образом, вы можете сделать очень схожую функцию, которая выполняет само присваивание.

function A = slice_assign(A, ix, dim, B)
%SLICE_ASSIGN Assign new values to a "slice" of A

subses = repmat({':'}, [1 ndims(A)]);
subses{dim} = ix;
A(subses{:}) = B;

На практике вам также может понадобиться функция, которая просто возвращает вычисленные индексы в массиве ячеек, поэтому вы можете переносить их и использовать их повторно для назначения и ссылки.

function out = slice_subs(A, ix, dim)

subses = repmat({':'}, [1 ndims(A)]);
subses{dim} = ix;
out = subses;

Ответ 2

Вы можете попробовать permute и setdiff переместить это измерение в согласованное положение:

function out = hslice(ndarray, d, i)
subdims = setdiff(1:ndims(ndarray),d);
sz = size(ndarray);
outsz = sz(subdims);
order = [d subdims];
ndarray = permute(ndarray,order);
out = reshape(ndarray(i,:),outsz);
end

Например:

d = 3; i = 2;
nd = randi(23,3,3,3,2);
out = hslice(nd,d,i);   % out = squeeze(nd(:,:,i,:)) for d=3

НО, данные переписываются до нарезки здесь, а не с кодом в вопросе. Итак, я бы действительно пошел с OP!