2014-09-24 2 views
3

Рассмотрит матрицуИндексация дубликаты в матрице: Matlab

X = [ 1 2 0 1; 
     1 0 1 2;           
     1 2 3 4;          
     2 4 6 8; 
      .   
      .       
     1 2 0 1     
      .     
      . ] 

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

Ans:

X = [ 1 2 0 1; y = [1 
     1 0 1 2;   1         
     1 2 3 4;   1        
     2 4 6 8;   1 
      .    . 
      .    .    
     1 2 0 1   2   
      .    .  
      . ]  .] 

Любые идеи?

ответ

1

Решение, включающее цикл for, может быть выполнено довольно легко, возможно, оно уже достаточно быстро. Я уверен, что есть более быстрое решение, которое может использовать cumsum, но, возможно, вам даже не нужно. Основная идея: сначала найти индексы уникальных строк, чтобы иметь возможность обрабатывать скалярные индексы вместо полных строк (векторов). Затем цикл по индексам и найти ряд предыдущих вхождений:

X = [ 1 2 0 1; 
    1 0 1 2;           
    1 2 3 4;          
    2 4 6 8;       
    1 2 0 1;     
    1 3 3 7;     
    1 2 0 1]; 

[~,~,idx] = unique(X, 'rows'); %// find unique rows 

%// loop over indices and accumulate number of previous occurences 
y = zeros(size(idx)); 
for i = 1:length(idx) 
    y(i) = sum(idx(1:i) == idx(i)); %// this line probably scales horrible with length of idx. 
end 

Результат для примера:

y = 

1 
1 
1 
1 
2 
1 
3 
+0

Это, кажется, быстро на самом деле в большинстве случаев. Я просто верю больше в векторизации, личный выбор, я думаю, так же потому, что я считаю, что графические процессоры имеют хороший шанс с векторизованными кодами. Итак, в качестве специального случая, когда я использовал 'X = randi (10,4000,2000);' и gpuArray для 'match_row_id' в моем решении, время выполнения фактически сравнимо с циклом цикла, и у меня есть достойный GPU для тестирования. +1 – Divakar

+0

@Divakar Мне также нравится вызов векторизации кода.Вы пытались переписать часть '' bsxfun'' с явным циклом for, а затем применить остальные ваши подходы? Хотя '' bsxfun'' предлагает использовать хороший и короткий код, он может быть медленнее, чем петлевые версии. Что касается оптимизации скорости, это может быть проблемой. – Nras

+0

Написание цикла для замены только части 'bsxfun' не приведет к улучшению. Я думаю, что в качестве кода цикла, ваш отлично. – Divakar

2

Подход № 1

%// unique rows 
unqrows = unique(X,'rows'); 

%// matches for each row against the unique rows and their cumsum values 
matches_perunqrow = squeeze(all(bsxfun(@eq,X,permute(unqrows,[3 2 1])),2)); 
cumsum_unqrows = cumsum(matches_perunqrow,1); 

%// Go through a row-order and get the cumsum values for the final output 
[row,col] = find(matches_perunqrow); 
[sorted_row,ind] = sort(row); 
y=cumsum_unqrows(sub2ind(size(cumsum_unqrows),[1:size(cumsum_unqrows,1)]',col(ind))); 

Пример запуска -

X = 
    1  2  0  1 
    1  0  1  2 
    1  2  3  4 
    2  4  6  8 
    1  2  0  1 
    1  2  3  4 
    1  2  3  4 
    1  2  3  4 
    1  2  3  4 
    1  2  0  1 
out = 
    1 
    1 
    1 
    1 
    2 
    2 
    3 
    4 
    5 
    3 

Подход № 2

%// unique rows 
unqrows = unique(X,'rows'); 

%// matches for each row against the unique rows 
matches_perunqrow = all(bsxfun(@eq,X,permute(unqrows,[3 2 1])),2) 

%// Get the cumsum of matches and select only the matches for each row. 
%// Since we need to go through a row-order, transpose the result 
cumsum_perrow = squeeze(cumsum(matches_perunqrow,1).*matches_perunqrow)' %//' 

%// Select the non zero values for the final output 
y = cumsum_perrow(cumsum_perrow~=0) 

Подход № 3

%// label each row based on their uniqueness 
[~,~,v3] = unique(X,'rows') 
matches_perunqrow = bsxfun(@eq,v3,1:size(X,1)) 

cumsum_unqrows = cumsum(matches_perunqrow,1); 

%// Go through a row-order and get the cumsum values for the final output 
[row,col] = find(matches_perunqrow); 
[sorted_row,ind] = sort(row); 
y=cumsum_unqrows(sub2ind(size(cumsum_unqrows),[1:size(cumsum_unqrows,1)]',col(ind))); 

Подход # 4

%// label each row based on their uniqueness 
[~,~,match_row_id] = unique(X,'rows'); 

%// matches for each row against the unique rows and their cumsum values 
matches_perunqrow = bsxfun(@eq,match_row_id',[1:size(X,1)]'); 
cumsum_unqrows = cumsum(matches_perunqrow,2); 

%// Select the cumsum values for the ouput based on the unique matches for each row 
y = cumsum_unqrows(matches_perunqrow); 
+1

Ум, добавляя некоторые объяснения? Это не совсем легко читаемо. Кроме того, некоторые быстрые тесты с вашим образцом не показали увеличения скорости по сравнению с моей версией цикла. 20000 repititions взяли 3,3 для моей версии и 4.7s и 3.6s для ваших двух версий. Возможно, это происходит для других пробных прогонов. Мне обычно нравятся ваши решения, но на этот раз я не вижу повышения удобочитаемости и скорости ... пока. – Nras

+0

@Nras Я добавляю комментарии, поскольку мы говорим на самом деле! :) – Divakar

+0

@Nras Отредактировано с комментариями. Возможно, возможны оптимизации, но для удаления 'squeeze' и' transposing'. Я тоже проверю скорость на моем конце. – Divakar

3

Как насчет этого?

y = sum(triu(squareform(pdist(X))==0)).'; 

Это работает путем подсчета количества предыдущих строк, равных каждой строке. Две строки равны, если их расстояние (рассчитанное с squareform и pdist) равно 0. triu гарантирует, что учитываются только предыдущие строки.

Для сокращения времени вычислений и избежать зависимости от статистики инструментов, вы можете использовать @ предложение user1735003 в:

y = sum(triu((bsxfun(@plus, sum(X.^2,2), sum(X.^2,2)') - 2*X*X.')==0)); 
+2

Могу ли я предложить sum (triu ((bsxfun (@plus, sum (X.^2,2), sum (X.^2,2) ') - 2 * X * X') == 0)) ' что не требует «pdist» статистического инструментария. – user1735003

+1

@ user1735003 Подумайте, что альтернативой будет - 'sum (triu (squeeze (sqrt (sum (bsxfun (@ минус, X, перестановка (X, [3 2 1])).^2,2))) == 0)) 'для уменьшения одной' суммы'. – Divakar

+0

... или, возможно, быстрее: 'sum (triu (squeeze (sqrt (sum (bsxfun (@ ne, X, permute (X, [3 2 1])), 2))) == 0))' @ Divakar –