【问题标题】:Indexing duplicates in a matrix: Matlab索引矩阵中的重复项:Matlab
【发布时间】:2014-09-24 06:45:46
【问题描述】:

考虑一个矩阵

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

我想创建一个新列,以便我可以对每一行的出现次数 ith 进行编号。

答案:

   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        
           .             .    
           .    ]        .]

有什么想法吗?

【问题讨论】:

    标签: matlab indexing


    【解决方案1】:

    这个怎么样?

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

    这通过计算有多少先前的行等于每行来工作。如果两行的距离(用squareformpdist 计算)为0,则两行相等。triu 确保只考虑前面的行。

    要减少计算时间并避免依赖统计工具箱,您可以使用@user1735003 的建议:

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

    【讨论】:

    • 我建议sum(triu((bsxfun(@plus, sum(X.^2,2), sum(X.^2,2)') - 2*X*X')==0)) 不需要统计工具箱的pdist
    • @user1735003 认为它的替代方案是 - sum(triu(squeeze(sqrt(sum(bsxfun(@minus,X,permute(X,[3 2 1])).^2,2)))==0)) 减少一个 sum
    • ... 或者,也许更快:sum(triu(squeeze(sqrt(sum(bsxfun(@ne,X,permute(X,[3 2 1])),2)))==0))@Divakar
    • @LuisMendo 聪明的举动!是的,这也适用于这种特殊情况。认为你应该添加它? +1
    • @Divar user1735003 的方法似乎更快。我想知道距离计算是否可以改进
    【解决方案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);
    

    【讨论】:

    • 介意添加一些解释吗?它不是很容易阅读。与我的循环版本相比,您的示例运行的一些快速测试也没有显示速度增加。我的版本 20000 次重复需要 3.3 秒,而你的两个版本需要 4.7 秒和 3.6 秒。也许它适用于其他样本运行。我通常喜欢你的解决方案,但这次我既没有看到可读性的提高,也没有看到速度的提高......但是。
    • @Nras 我实际上是在添加 cmets! :)
    • @Nras 用 cmets 编辑。尽管删除 squeezetransposing 可能会有一些优化。最后我也会检查速度。
    • 好吧,我很确定squeeze() 总是可以通过直接调用reshape() 来替换,如果不是懒得弄清楚尺寸的话。如果您正在做速度测试,我对此非常满意并留给您。前几天你在我的一个答案中做得很棒:-)。
    • 非常彻底!谢谢。
    【解决方案3】:

    包含 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
    

    【讨论】:

    • 在大多数情况下,这似乎确实很快。我只是更相信矢量化,我猜是个人选择,也因为我认为 GPU 有很好的机会使用矢量化代码。因此,作为一个特殊情况,当我在我的解决方案中使用X = randi(10,4000,2000); 和gpuArray for match_row_id 时,运行时实际上与循环代码相当,并且我有一个不错的GPU 进行测试。 +1
    • @Divakar 我也喜欢矢量化代码的挑战。您是否尝试使用显式 for 循环重写您的 bsxfun 部分,然后应用您的其余方法?尽管bsxfun 提供了漂亮而简短的代码,但它可能比循环版本慢。在优化速度方面,这很可能是个问题。
    • 编写一个 for 循环来替换 bsxfun 部分不会产生任何改进。我认为作为一个循环代码,你的代码是完美的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多