考虑每行上所有可能的排列,找到单元格数组的唯一行

时间:2016-10-10 09:47:03

标签: matlab performance permutation cell-array

我有维度m * k的单元格数组A

我希望将k唯一的行保持为k个单元格的顺序

"棘手" part "最多为k个单元格的顺序" :考虑i A行中的A(i,:)个单元格,{ {1}};可能有jAA(j,:),相当于A(i,:),直到对其k单元格进行重新排序,这意味着例如k=4可能是:

A{i,1}=A{j,2}
A{i,2}=A{j,3}
A{i,3}=A{j,1}
A{i,4}=A{j,4}

我现在正在做的是:

G=[0 -1 1; 0 -1 2; 0 -1 3; 0 -1 4; 0 -1 5; 1 -1 6; 1 0 6; 1 1 6; 2 -1 6; 2 0 6; 2 1 6; 3 -1 6; 3 0 6; 3 1 6]; 
h=7;
M=reshape(G(nchoosek(1:size(G,1),h),:),[],h,size(G,2));
A=cell(size(M,1),2);
for p=1:size(M,1)
    A{p,1}=squeeze(M(p,:,:)); 
    left=~ismember(G, A{p,1}, 'rows');
    A{p,2}=G(left,:); 
end

%To find equivalent rows up to order I use a double loop (VERY slow).
indices=[]; 
for j=1:size(A,1)
    if ismember(j,indices)==0 %if we have not already identified j as a duplicate
        for i=1:size(A,1)
            if i~=j
               if (isequal(A{j,1},A{i,1}) || isequal(A{j,1},A{i,2}))...
                  &&...
                  (isequal(A{j,2},A{i,1}) || isequal(A{j,2},A{i,2}))...
                  indices=[indices;i]; 
               end
            end
        end
    end
end
A(indices,:)=[];

它有效,但速度太慢。我希望有更快的东西可供我使用。

3 个答案:

答案 0 :(得分:6)

我想提出另一个想法,它与erfan's有一些概念上的相似之处。我的想法使用hash functions,特别是GetMD5 FEX submission

主要任务是如何将A中的每一行“减少”为单个代表值(例如字符向量),然后找到此向量的唯一条目。

根据基准与其他建议判断,我的答案并不像其中一个选择那样好,但我认为其存在理由在于它完全是数据类型不可知(在GetMD5 1 的限制范围内),该算法非常容易理解,它是A上操作的替代品,并且结果数组与原始方法获得的数组完全相同。当然,这需要编译器才能工作并且存在哈希冲突的风险(这可能会在非常罕见的情况下影响结果)。

以下是我计算机上典型运行的结果,后面是代码:

Original method timing:     8.764601s
Dev-iL's method timing:     0.053672s
erfan's method timing:      0.481716s
rahnema1's method timing:   0.009771s

function q39955559
G=[0 -1 1; 0 -1 2; 0 -1 3; 0 -1 4; 0 -1 5; 1 -1 6; 1 0 6; 1 1 6; 2 -1 6; 2 0 6; 2 1 6; 3 -1 6; 3 0 6; 3 1 6]; 
h=7;
M=reshape(G(nchoosek(1:size(G,1),h),:),[],h,size(G,2));
A=cell(size(M,1),2);
for p=1:size(M,1)
    A{p,1}=squeeze(M(p,:,:)); 
    left=~ismember(G, A{p,1}, 'rows');
    A{p,2}=G(left,:); 
end

%% Benchmark:
tic
A1 = orig_sort(A);
fprintf(1,'Original method timing:\t\t%fs\n',toc);

tic
A2 = hash_sort(A);
fprintf(1,'Dev-iL''s method timing:\t\t%fs\n',toc);

tic
A3 = erfan_sort(A);
fprintf(1,'erfan''s method timing:\t\t%fs\n',toc);

tic
A4 = rahnema1_sort(G,h);
fprintf(1,'rahnema1''s method timing:\t%fs\n',toc);

assert(isequal(A1,A2))
assert(isequal(A1,A3))
assert(isequal(numel(A1),numel(A4)))  % This is the best test I could come up with...

function out = hash_sort(A)
% Hash the contents:
A_hashed = cellfun(@GetMD5,A,'UniformOutput',false);
% Sort hashes of each row:
A_hashed_sorted = A_hashed;
for ind1 = 1:size(A_hashed,1)
  A_hashed_sorted(ind1,:) = sort(A_hashed(ind1,:));
end
A_hashed_sorted = cellstr(cell2mat(A_hashed_sorted));
% Find unique rows:
[~,ia,~] = unique(A_hashed_sorted,'stable');
% Extract relevant rows of A:
out = A(ia,:);

function A = orig_sort(A)
%To find equivalent rows up to order I use a double loop (VERY slow).
indices=[]; 
for j=1:size(A,1)
    if ismember(j,indices)==0 %if we have not already identified j as a duplicate
        for i=1:size(A,1)
            if i~=j
               if (isequal(A{j,1},A{i,1}) || isequal(A{j,1},A{i,2}))...
                  &&...
                  (isequal(A{j,2},A{i,1}) || isequal(A{j,2},A{i,2}))...
                  indices=[indices;i]; 
               end
            end
        end
    end
end
A(indices,:)=[];

function C = erfan_sort(A)
STR = cellfun(@(x) num2str((x(:)).'), A, 'UniformOutput', false);
[~, ~, id] = unique(STR);
IC = sort(reshape(id, [], size(STR, 2)), 2);
[~, col] = unique(IC, 'rows');
C = A(sort(col), :); % 'sort' makes the outputs exactly the same.

function A1 = rahnema1_sort(G,h)
idx = nchoosek(1:size(G,1),h);
%concatenate complements
M = [G(idx(1:size(idx,1)/2,:),:), G(idx(end:-1:size(idx,1)/2+1,:),:)];
%convert to cell so A1 is unique rows of A
A1 = mat2cell(M,repmat(h,size(idx,1)/2,1),repmat(size(G,2),2,1));

1 - 如果需要对更复杂的数据类型进行哈希处理,可以使用DataHash FEX submission代替,这有点慢。

答案 1 :(得分:4)

说明问题:识别数组中唯一行的理想选择是使用C = unique(A,'rows')。但是这里有两个主要问题,阻止我们在这种情况下使用这个功能。首先,在与其他行进行比较时,您希望计算每行的所有可能排列。如果A有5列,则表示每行检查120次不同的重新安排!听起来不可能。

第二个问题与unique本身有关;它does not accept cells except cell arrays of character vectors。因此,您不能简单地将A传递给unique并获得您期望的结果。

为什么要寻找替代方案?如你所知,因为目前它很慢:

With nested loop method:
------------------- Create the data (first loop):
Elapsed time is 0.979059 seconds.
------------------- Make it unique (second loop):
Elapsed time is 14.218691 seconds.

我的解决方案:

  1. 生成包含相同单元格的另一个单元格数组,但转换为字符串(STR)。
  2. 找到所有唯一元素的索引(id)。
  3. 使用唯一索引和排序行(IC)生成关联矩阵。
  4. 查找唯一的行(rows)。
  5. 收集AC)的相应行。
  6. 这是代码:

    disp('------------------- Create the data:')
    tic
    G = [0 -1 1; 0 -1 2; 0 -1 3; 0 -1 4; 0 -1 5; 1 -1 6; 1 0 6; ...
        1 1 6; 2 -1 6; 2 0 6; 2 1 6; 3 -1 6; 3 0 6; 3 1 6];
    h = 7;
    M = reshape(G(nchoosek(1:size(G,1),h),:),[],h,size(G,2));
    A = cell(size(M,1),2);
    for p = 1:size(M,1)
        A{p, 1} = squeeze(M(p,:,:));
        left = ~ismember(G, A{p,1}, 'rows');
        A{p,2} = G(left,:);
    end
    STR = cellfun(@(x) num2str((x(:)).'), A, 'UniformOutput', false);
    toc
    
    disp('------------------- Make it unique (vectorized):')
    tic
    [~, ~, id] = unique(STR);
    IC = sort(reshape(id, [], size(STR, 2)), 2);
    [~, col] = unique(IC, 'rows');
    C = A(sort(col), :); % 'sort' makes the outputs exactly the same.
    toc
    

    绩效考核:

    ------------------- Create the data:
    Elapsed time is 1.664119 seconds.
    ------------------- Make it unique (vectorized):
    Elapsed time is 0.017063 seconds.
    

    虽然初始化需要更多的时间和内存,但是在考虑所有排列的情况下,此方法在查找唯一行时速度更快。执行时间对A中的列数几乎不敏感。

答案 2 :(得分:3)

G似乎是一个误导性的观点。 这是少数nchoosek的结果

idx=nchoosek(1:4,2)
ans =

   1   2
   1   3
   1   4
   2   3
   2   4
   3   4

第一行是最后一行的补充

第二行是最后一行之前的一行

.....

因此,如果我们从{1 , 2}中提取行G,则其补码将为行{3, 4},依此类推。换句话说,如果我们假设G的行数为4,则G(idx(1,:),:)G(idx(end,:),:)的补充。

由于G行都是唯一的,因此所有A{m,n}的大小始终相同。

A{p,1}A{p,2}是彼此的补充。 A的唯一行的大小为size(idx,1)/2

所以不需要任何循环或进一步比较:

h=7;
G = [0 -1 1; 0 -1 2; 0 -1 3; 0 -1 4; 0 -1 5; 1 -1 6; 1 0 6; ...
    1 1 6; 2 -1 6; 2 0 6; 2 1 6; 3 -1 6; 3 0 6; 3 1 6];
idx = nchoosek(1:size(G,1),h);
%concatenate complements
M = [G(idx(1:size(idx,1)/2,:).',:), G(idx(end:-1:size(idx,1)/2+1,:).',:)];
%convert to cell so A1 is unique rows of A
A1 = mat2cell(M,repmat(h,size(idx,1)/2,1),repmat(size(G,2),2,1));

更新:上述方法效果最好但是如果想要从A以外的A获得A1我建议遵循基于erfan'的方法。秒。我们可以直接使用数组来代替将数组转换为字符串:

STR=reshape([A.'{:}],numel(A{1,1}),numel(A)).';
[~, ~, id] = unique(STR,'rows');

IC = sort(reshape(id, size(A, 2),[]), 1).';
[~, col] = unique(IC, 'rows');
C1 = A(sort(col), :);

由于我使用Octave,我目前无法运行mex文件,因此我无法测试Dev-iL的方法

<强>结果

erfan method (string):  4.54718 seconds.
rahnema1 method (array): 0.012639 seconds.

Online Demo