Как мне перебрать каждый элемент в n-мерной матрице в MATLAB?

87

У меня проблема. Мне нужно перебрать каждый элемент n-мерной матрицы в MATLAB. Проблема в том, что я не знаю, как это сделать для произвольного количества измерений. Я знаю, что могу сказать

for i = 1:size(m,1)
    for j = 1:size(m,2)
        for k = 1:size(m,3)

и так далее, но есть ли способ сделать это для произвольного количества измерений?

rlbond
источник
13
Примечание по терминологии Matlab: Matlab имеет небольшое количество основных типов данных. Наиболее важные из них: структура, матрица и массив ячеек. При обращении к частям матрицы обычно используется термин «элемент» и зарезервирован термин «ячейка» для обозначения частей массива ячеек. Массивы ячеек и матрицы имеют множество синтаксических и семантических различий, хотя оба являются N-мерными структурами данных.
Mr Fooz
3
Могу я спросить, для чего вам нужна итерация? Может быть, есть «векторизованный» способ сделать это вместо этого ...
Хосам Али

Ответы:

92

Вы можете использовать линейную индексацию для доступа к каждому элементу.

for idx = 1:numel(array)
    element = array(idx)
    ....
end

Это полезно, если вам не нужно знать, на каком i, j, k вы находитесь. Однако, если вам не нужно знать, какой у вас индекс, вам, вероятно, лучше использовать arrayfun ()

Андрей
источник
1
Кроме того , если вы хотите , чтобы восстановить показатели по какой - то причине, вы могли бы с помощью этих двух простых команд: I = cell(1, ndims(array)); [I{:}] = ind2sub(size(array),idx);.
knedlsepp 04
34

Идея линейного индекса для массивов в Matlab очень важна. Массив в MATLAB - это просто вектор элементов, размещенных в памяти. MATLAB позволяет использовать либо индекс строки и столбца, либо единственный линейный индекс. Например,

A = magic(3)
A =
     8     1     6
     3     5     7
     4     9     2

A(2,3)
ans =
     7

A(8)
ans =
     7

Мы можем увидеть порядок, в котором элементы хранятся в памяти, развернув массив в вектор.

A(:)
ans =
     8
     3
     4
     1
     5
     9
     6
     7
     2

Как видите, 8-й элемент - это номер 7. Фактически, функция find возвращает свои результаты в виде линейного индекса.

find(A>6)
ans =
     1
     6
     8

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

B = zeros(size(A));
for i = 1:numel(A)
  B(i) = A(i).^2;
end

B
B =
    64     1    36
     9    25    49
    16    81     4

Есть много обстоятельств, когда линейный индекс более полезен. Преобразование между линейным индексом и двухмерными (или более высокими) индексами выполняется с помощью функций sub2ind и ind2sub.

Линейный индекс обычно применяется к любому массиву в Matlab. Таким образом, вы можете использовать его для структур, массивов ячеек и т. Д. Единственная проблема с линейным индексом - это когда они становятся слишком большими. MATLAB использует 32-битное целое число для хранения этих индексов. Поэтому, если в вашем массиве более 2 ^ 32 элементов, линейный индекс не сработает. Это действительно проблема только в том случае, если вы часто используете разреженные матрицы, когда иногда это вызывает проблемы. (Хотя я не использую 64-битную версию MATLAB, я считаю, что эта проблема была решена для тех счастливчиков, которые ее используют.)


источник
Индексирование в 64-битном MATLAB действительно правильно разрешает 64-битные индексы. Например: x = ones(1,2^33,'uint8'); x(2^33)работает как положено.
Эдрик
@Edric - Конечно, это поведение, несомненно, изменилось бы за годы (и во многих выпусках) с тех пор, как я сделал это заявление. Спасибо за проверку.
:) Я не осознавал, сколько лет был ответ, пока не прокомментировал - вопрос только что появился в моем RSS-канале, и я даже не заметил, что ответил на него тоже!
Эдрик
15

Как указано в нескольких других ответах, вы можете перебирать все элементы в матрице A(любого измерения), используя линейный индекс от 1до numel(A)в одном цикле for. Вы также можете использовать несколько функций: arrayfunи cellfun.

Давайте сначала предположим, что у вас есть функция, которую вы хотите применить к каждому элементу A(вызываемого my_func). Сначала вы создаете дескриптор функции для этой функции:

fcn = @my_func;

Если Aэто матрица (типа double, single и т. Д.) Произвольной размерности, вы можете использовать arrayfunдля применения my_funcк каждому элементу:

outArgs = arrayfun(fcn, A);

Если Aэто массив ячеек произвольной размерности, вы можете использовать cellfunдля применения my_funcк каждой ячейке:

outArgs = cellfun(fcn, A);

Функция my_funcдолжна принимать Aв качестве входных данных. Если есть какие-либо выходы из my_func, они помещаются в outArgs, который будет того же размера / размера, что и A.

Одно предостережение по поводу выходов ... если my_funcвозвращает выходные данные разных размеров и типов, когда он работает с разными элементами A, тогда outArgsнеобходимо будет преобразовать их в массив ячеек. Это делается путем вызова либо arrayfunили cellfunс дополнительной парой параметр / значение:

outArgs = arrayfun(fcn, A, 'UniformOutput', false);
outArgs = cellfun(fcn, A, 'UniformOutput', false);
Гновице
источник
13

Еще одна хитрость - использовать ind2subи sub2ind. В сочетании с numelи sizeэто может позволить вам делать что-то вроде следующего, который создает N-мерный массив, а затем устанавливает для всех элементов на «диагонали» значение 1.

d = zeros( 3, 4, 5, 6 ); % Let's pretend this is a user input
nel = numel( d );
sz = size( d );
szargs = cell( 1, ndims( d ) ); % We'll use this with ind2sub in the loop
for ii=1:nel
    [ szargs{:} ] = ind2sub( sz, ii ); % Convert linear index back to subscripts
    if all( [szargs{2:end}] == szargs{1} ) % On the diagonal?
        d( ii ) = 1;
    end
end
Эдрик
источник
+1 за демонстрацию хорошего примера того, как MATLAB нарушает утиную печать.
Phillip Cloud
1

Вы можете заставить рекурсивную функцию выполнять работу

  • Позволять L = size(M)
  • Позволять idx = zeros(L,1)
  • Принимаем length(L)за максимальную глубину
  • Петля for idx(depth) = 1:L(depth)
  • Если ваша глубина равна length(L), выполните операцию элемента, иначе вызовите функцию снова с помощьюdepth+1

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

Деннис Джахеруддин
источник
1

эти решения быстрее (примерно на 11%), чем при использовании numel;)

for idx = reshape(array,1,[]),
     element = element + idx;
end

или

for idx = array(:)',
    element = element + idx;
end

UPD. tnx @rayryeng за обнаруженную ошибку в последнем ответе


Отказ от ответственности

Информация о времени, на которую ссылается этот пост, неверна и неточна из-за фундаментальной опечатки (см. Поток комментариев ниже, а также историю редактирования - особенно посмотрите на первую версию этого ответа). Будьте внимательны, Emptor .

корова
источник
1
1 : array(:)эквивалентно 1 : array(1). Это не повторяет все элементы, поэтому время выполнения у вас быстрое. Более того, randгенерирует числа с плавающей запятой , и это 1 : array(:)приведет к созданию пустого массива, поскольку ваш оператор пытается найти увеличивающийся вектор с его начальным значением, равным 1, с конечным значением в виде числа с плавающей запятой с диапазоном, [0,1)исключающим 1 при увеличении шаги 1. Нет такого возможного вектора, что приводит к пустому вектору. Ваш forцикл не запускается, поэтому ваше утверждение неверно. -1 голос. извиняюсь.
rayryeng
@rayryeng, ты не прав. array (:) не эквивалентно 1: array (1). Это нравится reshape(...).
mathcow
@rayryeng matlab r2013a + linux - работает! ;) Я тоже только что запустил этот код
mathcow
Введите в 1 : array(:)в командной строке после создания array . Получается пустая матрица? если да, то ваш код не работает. Я оставляю свой голос, потому что вы даете ложную информацию.
Rayryeng
@rayryeng, я понимаю! да, вы правы, извините за глупый спор
mathcow 09
-1

Если вы посмотрите глубже на другие варианты использования, sizeвы увидите, что на самом деле вы можете получить вектор размера каждого измерения. Эта ссылка показывает вам документацию:

www.mathworks.com/access/helpdesk/help/techdoc/ref/size.html

После получения вектора размера переберите этот вектор. Что-то вроде этого (простите за синтаксис, так как я не использовал Matlab с колледжа):

d = size(m);
dims = ndims(m);
for dimNumber = 1:dims
   for i = 1:d[dimNumber]
      ...

Превратите это в действительный синтаксис Matlab-legal, и я думаю, он будет делать то, что вы хотите.

Кроме того, вы должны иметь возможность выполнять линейное индексирование, как описано здесь .

Эрих Мирабал
источник
Я не совсем понимаю, как этот порядок циклов будет перебирать все элементы матрицы. Например, если у вас есть матрица 3 на 4 (с 12 элементами), ваш внутренний цикл будет повторяться только 7 раз.
gnovice
он должен проходить по каждому измерению матрицы. Внешний цикл повторяется по размеру, внутренний цикл по размеру этого измерения. По крайней мере, такова идея. Как утверждают все остальные, если все, что он хочет, - это каждая ячейка, лучше всего подходит линейная индексация. Если он хочет перебрать каждое измерение, ему придется сделать что-то подобное.
Эрих Мирабал
также спасибо за редактирование. моя ссылка была немного запутанной и просто не могла работать правильно, используя обычный способ связывания. Кроме того, чтобы расширить мое утверждение: ему все равно придется выполнять множество других операций по отслеживанию индекса (используя счетчик или что-то в этом роде). Я думаю, вам или Эндрю будет проще то, что, я думаю, он пытается делать.
Эрих Мирабал,
-1

Вы хотите смоделировать n-вложенные циклы for.

Итерацию по n-мерному массиву можно рассматривать как увеличение n-значного числа.

Для каждого размера у нас есть столько цифр, сколько длина этого измерения.

Пример:

Допустим, у нас есть массив (матрица)

int[][][] T=new int[3][4][5];

в «для обозначений» имеем:

for(int x=0;x<3;x++)
   for(int y=0;y<4;y++)
       for(int z=0;z<5;z++)
          T[x][y][z]=...

чтобы смоделировать это, вам нужно будет использовать "n-значное число"

У нас есть 3-значное число, с 3 цифрами для первой, 4 для второй и пяти для третьей цифры.

Нам нужно увеличить число, чтобы получилась последовательность

0 0 0
0 0 1
0 0 2    
0 0 3
0 0 4
0 1 0
0 1 1
0 1 2
0 1 3
0 1 4
0 2 0
0 2 1
0 2 2
0 2 3
0 2 4
0 3 0
0 3 1
0 3 2
0 3 3
0 3 4
and so on

Таким образом, вы можете написать код для увеличения такого n-значного числа. Вы можете сделать это таким образом, что можете начать с любого значения числа и увеличивать / уменьшать цифры на любые числа. Таким образом вы можете моделировать вложенные циклы for, которые начинаются где-то в таблице и заканчиваются не в конце.

Однако это непростая задача. К сожалению, я не могу помочь с обозначением Matlab.

бмегли
источник