индексы появления каждой строки в MATLAB

У меня есть две матрицы, A и B. (B непрерывен, как 1:n)

Мне нужно найти все вхождения каждой отдельной строки B в A и соответственно сохранить эти индексы строк в массиве ячеек C. См. пример ниже.

A = [3,4,5;1,3,5;1,4,3;4,2,1]
B = [1;2;3;4;5]

Таким образом,

C = {[2,3,4];[4];[1,2,3];[1,3,4];[1,2]}

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

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

(Чтобы дать вам контекст, цель этого состоит в том, чтобы идентифицировать в сетке те грани, которые прикреплены к одной вершине. Обратите внимание, что я не могу использовать функцию edgeattachments, потому что мои данные не имеют форму «TR» в представлении триангуляции. Все, что у меня есть, это список граней и список вершин.)


person Ashwin J. Shahani    schedule 04.10.2013    source источник
comment
Всегда ли B непрерывен, как 1:n? Это облегчило бы работу, поскольку вам не нужно было бы проверять B в A, а нужно было бы проверять каждую строку A и заполнять ячейку C по мере появления индексов.   -  person Werner    schedule 04.10.2013
comment
@Ashwin: Ваше выражение для C неверно, но я предполагаю, что вы имеете в виду C = {[2,3,4];[4];[1,2,3];[1,3,4];[1,2]}. Это правильно? Если да, то смотрите мой ответ. Надеюсь, это поможет.   -  person chappjc    schedule 04.10.2013
comment
@ Вернер, да, это правильно. Я обновил постановку задачи с этой деталью.   -  person Ashwin J. Shahani    schedule 04.10.2013
comment
@chappjc, да, это тоже правильно. Обновлено.   -  person Ashwin J. Shahani    schedule 04.10.2013


Ответы (2)


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

% No fancy stuff, just fast and furious 
bMax = numel(B);
nRows = size(A,1);

cLogical = sparse(nRows,bMax);

for curRow = 1:nRows
  curIdx = A(curRow,:);
  cLogical(curRow,curIdx) = 1;
end

Отвечать:

cLogical =

   (2,1)        1
   (3,1)        1
   (4,1)        1
   (4,2)        1
   (1,3)        1
   (2,3)        1
   (3,3)        1
   (1,4)        1
   (3,4)        1
   (4,4)        1
   (1,5)        1
   (2,5)        1

Как прочитать ответ. Для каждого столбца строки показывают индексы, которые индекс столбца появляется в A. То есть 1 появляется в строках [2 3 4], 2 появляется в строке [4], 3 строк [1 2 3], 4 строке [1 3 4], 5 в строке [1 2].

Затем вы можете использовать cLogical вместо ячейки в качестве матрицы индексации в будущем для своих нужд.

Другим способом было бы выделить C с ожидаемым значением того, сколько раз индекс должен появиться в C.

% Fancier solution using some assumed knowledge of A
bMax = numel(B);
nRows = size(A,1);
nColumns = size(A,2);

% Pre-allocating with the expected value, an attempt to reduce re-allocations.
% tic; for rep=1:10000; C = mat2cell(zeros(bMax,nColumns),ones(1,bMax),nColumns); end; toc 
% Elapsed time is 1.364558 seconds.
% tic; for rep=1:10000; C = repmat({zeros(1,nColumns)},bMax,1); end; toc
% Elapsed time is 0.606266 seconds.
% So we keep the not fancy repmat solution
C = repmat({zeros(1,nColumns)},bMax,1);
for curRow = 1:nRows
  curIdxMsk = A(curRow,:);
  for curCol = 1:nColumns
    curIdx = curIdxMsk(curCol);
    fillIdx = ~C{curIdx};
    if any(fillIdx) 
      fillIdx = find(fillIdx,1);
    else
      fillIdx = numel(fillIdx)+1;
    end
    C{curIdx}(fillIdx) = curRow;
  end
end

% Squeeze empty indexes:
for curRow = 1:bMax
  C{curRow}(~C{curRow}) = [];
end

Отвечать:

>> C{:}

ans =

     2     3     4


ans =

     4


ans =

     1     2     3


ans =

     1     3     4


ans =

     1     2

Какое решение будет работать лучше всего? Вы выполняете тест производительности в своем коде, потому что он зависит от размера A, bMax, объема памяти вашего компьютера и так далее. Тем не менее, мне все еще любопытны решения, которые другие люди могут сделать для этого x). Мне понравилось решение chappjc, хотя у него есть минусы, на которые он указал.

Для данного примера (10 тыс. раз):

Solution 1: Elapsed time is 0.516647 seconds. 
Solution 2: Elapsed time is 4.201409 seconds (seems that solution 2 is a bad idea hahaha, but since it was created to the specific issue of A having many rows it has to be tested in those conditions).
chappjc' solution: Elapsed time is 2.405341 seconds.
person Werner    schedule 04.10.2013
comment
Ваши сроки для более крупного примера? Когда я запускаю свое (неэффективное использование памяти) решение с коротким исходным примером, я получаю Elapsed time is 0.000707 seconds.. - person chappjc; 04.10.2013
comment
@chappjc Я забыл сказать, что запускал его 10 тысяч раз. - person Werner; 04.10.2013
comment
Ха-ха! Что объясняет его. Но более честным тестом было бы использование больших данных, а не большего количества итераций. bsxfun действительно начинает кричать с большими данными и не сильно поможет при повторном вызове для коротких данных. Кроме того, я обновил свой ответ вариантом разреженного вывода, который вы предложили в своем первом ответе. Это должно быть довольно быстро, но, к сожалению, все еще не очень хорошо для использования памяти. - person chappjc; 04.10.2013
comment
@chappjc Я только что пробежал 10 000 раз, чтобы удалить возможные временные колебания (я работал, слушая музыку и так далее, эти фоновые процессы могли мешать тесту, но я не хотел останавливать музыку, ха-ха). Да и за большими данными бегать тоже не хотелось, было бы скучно создавать выборку данных для этого и ждать запуска процесса, другое дело думать и так далее. OP может проверить производительность и рассказать нам, что лучше всего подходит для его образца (что-то, что я хотел бы знать, вместе с размерами исходных данных). - person Werner; 04.10.2013
comment
@ Вернер, твое решение №1 - это именно то, что мне нужно. Очень умный! Спасибо. - person Ashwin J. Shahani; 04.10.2013
comment
@AshwinJ.Shahani рад, что смог помочь x) - person Werner; 04.10.2013

Мы можем сделать это, не делая никаких предположений о B. Попробуйте использовать bsxfun и mat2cell следующим образом:

M = squeeze(any(bsxfun(@eq,A,permute(B,[3 2 1])),2)); % 4x3x1 @eq 1x1x5 => 4x3x5
R = sum(M); % 4x5 -> 1x5
[ii,jj] = find(M);
C = mat2cell(ii,R)

Ячейки в C выше будут векторами-столбцами, а не строками, как в вашем примере. Чтобы ячейки содержали векторы-строки, используйте вместо этого C = mat2cell(ii',1,R)'.

Меня беспокоит только то, что mat2cell может быть медленным для миллионов значений R, но если вы хотите, чтобы ваш вывод был в ячейке, я не уверен, насколько лучше вы можете это сделать. РЕДАКТИРОВАТЬ: если вы можете работать с разреженной матрицей, как в первом решении Вернера с циклом, замените последнюю строку приведенного выше на следующее:

>> Cs = sparse(ii,jj,1)
Cs =
   (2,1)        1
   (3,1)        1
   (4,1)        1
   (4,2)        1
   (1,3)        1
   (2,3)        1
   (3,3)        1
   (1,4)        1
   (3,4)        1
   (4,4)        1
   (1,5)        1
   (2,5)        1

К сожалению, bsxfun, вероятно, исчерпает память, если оба size(A,1) и numel(B) большие! Возможно, вам придется перебрать элементы A или B, если память становится проблемой. Вот один из способов сделать это, перебирая вершины в B:

for i=1:numel(B), C{i} = find(any(A==B(i),2)); end

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

person chappjc    schedule 04.10.2013
comment
Действительно, у меня проблемы с памятью с bsxfun... И я должен был указать: это не обязательно должно быть в массиве ячеек. Я предложил этот формат только потому, что векторы в каждой строке имеют разную длину. Я думаю, было бы хорошо, если бы другие (пустые) записи C (матрицы) были равны 0. Спасибо! - person Ashwin J. Shahani; 04.10.2013