【问题标题】:Finding (multiset) difference between two arrays查找(多集)两个数组之间的差异
【发布时间】:2019-01-20 14:45:46
【问题描述】:

给定数组(比如行向量)A 和 B,我如何找到一个数组 C,使得合并 B 和 C 会得到 A?

例如,给定

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

然后

C = multiset_diff(A, B) % Should be [4, 6, 4, 3, 1, 5]

(结果的顺序在这里无关紧要)。

对于同一个A,如果B = [2, 4, 5],那么结果应该是[6, 4, 3, 3, 1, 5, 5]

(由于 A 中有两个 4s 和 B 中有一个 4,因此结果 C 中应该有 2 - 1 = 1 4。对于其他值也是如此。)

PS:请注意,setdiff 将删除所有 2、3 和 5 的实例,而在这里,无论它们在 B 中出现多少次,它们都需要被删除。


性能:我在本地运行了一些 quick-n-dirty 基准测试,以下是结果供将来参考:

  • @heigele 的嵌套循环方法在 A 的小长度(比如最多 N = 50 个左右的元素)中表现最佳。与下一个最佳方法相比,它对小型 (N=20) As 的效果提高了 3 倍,对中型 (N=50) As 的效果提高了 1.5 倍——

    李>
  • @obchardon 的基于histc 的方法。当 A 的大小 N 开始为 100 及以上时,这是表现最好的。例如,当 N = 200 时,这比上述嵌套循环方法好 3 倍。

@matt 的 for+find 方法与小 N 的 histc 方法相当,但对于较大的 N,性能会迅速下降(这是有道理的,因为每次迭代都会运行整个 C == B(x) 比较)。

(在撰写本文时,其他方法要么慢几倍,要么无效。)

【问题讨论】:

  • 合并是什么意思?如何将 [2,3,5,5] 与 [4,6,4,3,1,5] 合并得到 [2,4,6,4,3,3,1,5,5,5] ?你只想要任何随机顺序的相同数值吗?这样D = cat(2,B,C)?
  • @Matt “任何随机顺序的相同数值” 差不多,是的。这里的“合并”是概念性的,而不是特定的算法过程,旨在说明预期结果会是什么样子。使用D = cat(2, B, C),那么 D 应该是 A 的排列。
  • 有趣的结果!是时候将马特的答案标记为已接受了:)

标签: arrays matlab multiset


【解决方案1】:

这是一种矢量化的方式。内存效率低下,主要是为了好玩:

tA = sum(triu(bsxfun(@eq, A, A.')), 1);
tB = sum(triu(bsxfun(@eq, B, B.')), 1);
result = setdiff([A; tA].', [B; tB].', 'rows', 'stable');
result = result(:,1).';

这个想法是通过使用出现编号标记每个条目来使其唯一。向量变为 2 列矩阵,setdiff'rows' 选项一起应用,然后从结果中删除标签。

【讨论】:

  • 聪明的方法。我开始沿着那条路走,但没有看到从bsxfun 树林出来的路。干得好,先生。
【解决方案2】:

我不喜欢循环,但对于 A 的随机扰动,这是我想出的最好的方法。

C = A;
for x = 1:numel(B)
C(find(C == B(x), 1, 'first')) = [];
end

我很好奇A 的不同顺序对解决方案的影响,所以我设置了一个这样的测试:

Ctruth = [1 3 3 4 5 5 6];
for testNumber = 1:100
    Atest = A(randperm(numel(A)));
    C = myFunction(Atest,B);
    C = sort(C);
    assert(all(C==Ctruth));
end

【讨论】:

  • 我即将接受这一点,因为这是一种直接的方法,也是这里的答案中最快的(根据我尝试的一些快速本地基准测试)。但是您能否重新排列答案,以便找出差异的代码先出现,以便将来的读者更清楚?
  • @sundar 有道理,我已将代码粘贴在顶部。这有什么帮助。
  • 感谢您更新帖子。不幸的是,当 A 开始变大时,这种方法似乎不能很好地扩展,快速增长慢几倍(正如问题中的更新所提到的)。所以我最终可能会接受@obchardon 的回答,因为这样可以更好地扩展。对不起!
  • @sundar 这正是我通常不使用for 循环的原因,但对于简单的示例(如您提供的示例),这通常不是问题。
【解决方案3】:

使用histc 函数的另一种方法:

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

uA  = unique(A);
hca = histc(A,uA); 
hcb = histc(B,uA);
res = repelem(uA,hca-hcb)

我们简单地根据向量A的唯一值计算每个向量的重复元素个数,然后我们使用repelem来创建结果。

此解决方案不保留初始顺序,但对您来说似乎不是问题。

我使用 histc 是为了兼容 Octave,但此功能已弃用,因此您也可以使用 histcounts

【讨论】:

    【解决方案4】:

    您可以使用ismember 的第二个输出来查找B 的元素在A 中的索引,并使用diff 删除重复项:

    此答案假定 B 已排序。如果不是这种情况,则必须在执行上述解决方案之前对B 进行排序。

    第一个例子:

    A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
    B = [2, 3, 5, 5];
    %B = sort(B); Sort if B is not sorted.
    [~,col] = ismember(B,A);
    indx = find(diff(col)==0);
    col(indx+1) = col(indx)+1;
    A(col) = [];
    C = A;
    
    >>C
    
    4     6     4     3     1     5
    

    第二个例子:

    A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
    B = [2, 4, 5, 5];
    %B = sort(B); Sort if B is not sorted.
    [~,col] = ismember(B,A);
    indx = find(diff(col)==0);
    col(indx+1) = col(indx)+1;
    A(col) = [];
    C = A;
    >>C
    
    6     4     3     3     1     5
    

    【讨论】:

    • @sundar 我已经更新了我的答案,解决方案适用于两个示例。
    • @sundar 是的,这是假设。谢谢,我已经用您的正确评论编辑了答案。
    • 哦,这似乎也假设 B 最多只能有 2 次重复元素 - 例如,B = [2, 4, 5, 5, 5] 不返回 [6, 4, 3, 3, 1],而是在 5 中留下.
    • 你应该考虑下次让你的问题更清楚。
    【解决方案5】:

    受到 Matt 的强烈启发,但在我的机器上速度提高了 40%:

    function A = multiDiff(A,B)
    for j = 1:numel(B)
        for i = 1:numel(A)
            if A(i) == B(j)
                A(i) = [];
                break;
            end
        end
    end
    end
    

    【讨论】:

      猜你喜欢
      • 2020-01-11
      • 1970-01-01
      • 2018-01-07
      • 2021-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      • 2016-07-04
      相关资源
      最近更新 更多