Как все размеры, оставшиеся после указанного, сохраняются без явного их перечисления?

Или, что то же самое, "что эквивалентно индексированию NumPy-эллипсиса в Matlab"

Скажем, у меня есть многомерный массив:

x = zeros(3, 4, 5, 6);

Я хочу написать функцию, которая принимает массив размером (3, ...) и выполняет некоторые вычисления. В NumPy я мог бы написать это:

def fun(x):
    return x[0]*x[1] + x[2]

Однако эквивалент в MATLAB не работает, поскольку индексирование с одним целым выравнивает массив до 1d

function y = fun_bad(x)
    y = x(1)*x(2) + x(3)

Я могу сделать эту работу до 3-мерных массивов с помощью

function y = fun_ok3d(x)
    y = x(1,:,:)*x(2,:,:) + x(3,:,:)

Если я хочу, чтобы это работало до 10-мерных массивов, я могу написать

function y = fun_ok10d(x)
    y = x(1,:,:,:,:,:,:,:,:,:)*x(2,:,:,:,:,:,:,:,:,:) + x(3,:,:,:,:,:,:,:,:,:)

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

NumPy может использовать литерал ... (Ellipsis) в выражении индексации, означающий ": столько раз, сколько необходимо", что позволило бы решить эту проблему.

Ответы

Ответ 1

Подход 1: использование списка, разделенного запятыми, с ':'

Я не знаю, как указать

: столько раз, сколько необходимо

сохраняя форму. Но вы можете указать

: произвольное число раз

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

Это делается с помощью списка, разделенного запятыми, сгенерированного из массива ячеек, и используя тот факт, что строка ':' может использоваться как индекс вместо ::

function y = fun(x)
colons = repmat({':'}, 1, ndims(x)-1); % row cell array containing the string ':'
                                       % repeated the required number of times
y = x(1,colons{:}).*x(2,colons{:}) + x(3,colons{:});

Этот подход легко обобщается на индексирование по любому размеру, а не только по первому:

function y = fun(x, dim)
% Input argument dim is the dimension along which to index
colons_pre = repmat({':'}, 1, dim-1);
colons_post = repmat({':'}, 1, ndims(x)-dim);
y = x(colons_pre{:}, 1, colons_post{:}) ...
  .*x(colons_pre{:}, 2, colons_post{:}) ...
  + x(colons_pre{:}, 3, colons_post{:});

Подход 2: разбиение массива

Вы можете разделить массив по первому размеру с помощью num2cell, а затем применить операцию к полученным подмассивам. Конечно, это использует больше памяти; и как отмеченный @Adriaan, он медленнее.

function y = fun(x)
xs = num2cell(x, [2:ndims(x)]); % x split along the first dimension
y = xs{1}.*xs{2} + xs{3};

Или, для индексирования вдоль любого измерения:

function y = fun(x, dim)
xs = num2cell(x, [1:dim-1 dim+1:ndims(x)]); % x split along dimension dim
y = xs{1}.*xs{2} + xs{3};

Ответ 2

MATLAB выравнивает все конечные размеры при использовании одного двоеточия, поэтому вы можете использовать его для получения из вашего массива ND в 2D-массив, который вы можете reshape обратно в исходные размеры N после расчета.

Вдоль первого измерения

Если вы хотите использовать первое измерение, вы можете использовать относительно простой и короткий фрагмент кода:

function y = MyMultiDimensional(x)
    x_size = size(x); % Get input size
    yflat = x(1,:) .* x(2,:) + x(3,:); % Calculate "flattened" 2D function
    y = reshape(yflat, [1 x_size(2:end)]); % Reshape output back to original size
end

Наряду с произвольной размерностью, теперь показывающей перестановку N-D.

Если вы хотите, чтобы ваша функция действовала по n-му размеру из общего числа N, вы можете permute, что измерение передняя часть:

function y = MyMultiDimensional(x,n)
    x_size = size(x); % Get input size

    Order = 1:numel(x_size);
    Order(n)=[]; % Remove n-th dimension
    Order2 = [n, Order]; % Prepend n-th dimension

    xPermuted = permute(x,Order2); % permute the n-th dimension to the front
    yTmp = xPermuted (1,:) .* xPermuted (2,:) + xPermuted (3,:); % Calculate "flattened" 2D function
    y = reshape(yTmp, x_size(Order)); % Reshape output back to original size
end

Я приурочил результаты двух методов Луиса и моих методов:

function timeMultiDim()

x = rand(1e1,1e1,1e1,1e1,1e1,1e1,1e1,1e1);

    function y = Luis1(x)
        colons = repmat({':'}, 1, ndims(x)-1); % row cell array containing the string ':'
        % repeated the required number of times
        y = x(1,colons{:}).*x(2,colons{:}) + x(3,colons{:});

    end

    function y = Luis2(x)
        xs = num2cell(x, [2:ndims(x)]); % x split along the first dimension
        y = xs{1}.*xs{2} + xs{3};
    end

    function y = Adriaan(x)
        x_size = size(x); % Get input size
        yflat = x(1,:) .* x(2,:) + x(3,:); % Calculate "flattened" 2D function
        y = reshape(yflat, [1 x_size(2:end)]); % Reshape output back to original size
    end

n=1;
    function y = Adriaan2(x,n)
        x_size = size(x); % Get input size

        Order = 1:numel(x_size);
        Order(n)=[]; % Remove n-th dimension
        Order2 = [n, Order]; % Prepend n-th dimension

        xPermuted = permute(x,Order2); % permute the n-th dimension to the front
        yTmp = xPermuted (1,:) .* xPermuted (2,:) + xPermuted (3,:); % Calculate "flattened" 2D function
        y = reshape(yTmp, x_size(Order)); % Reshape output back to original size

    end

t1 = timeit(@() Luis1(x));
t2 = timeit(@() Luis2(x));
t3 = timeit(@() Adriaan(x));
t4 = timeit(@() Adriaan2(x,n));

format long g;
fprintf('Luis 1: %f seconds\n', t1);
fprintf('Luis 2: %f seconds\n', t2);
fprintf('Adriaan 1: %f seconds\n', t3);
fprintf('Adriaan 2: %f seconds\n', t4);

end

Luis 1: 0.698139 seconds
Luis 2: 4.082378 seconds
Adriaan 1: 0.696034 seconds
Adriaan 2: 0.691597 seconds

Итак, переход в ячейку плох, это занимает более чем в 5 раз, reshape и ':' едва обособлены, так что это сведено к предпочтению.