比较两组不同类型的有效方法

Efficient way to compare two sets of different type

首先,我需要一些非常有效的解决方案,因为我正在比较具有 >300k 元素的集合。

一开始我们有两个不同的classes

Class A {
   String keyA;
   String keyB;
   String keyC;
}

Class B {
   String keyA;
   String keyB;
   String keyC;
   String name;
   String code;

   toA() {
     return new A(keyA, keyB, keyC);
   }
}

它们都包含几个组成key的字段(本例中的三列key = keyA keyB keyC)

这个组合键使得使用嵌套循环的原始蛮力计算非常长。 所以我发现最有效的方法是使用方法 toA 将第二个 class 转换为第一个 然后我可以安全地比较它们使用例如 google 的 api 使用 Sets efficiency

Set<A> collectionA = <300k of elements>
Set<B> collectionB = <300k of elements>
Set<A> collectionBConvertedToA = collectionB.stream().map(item -> item.toA()).collect(toSet())

Set<A> result = Sets.differences(collectionBConvertedToA, collectionA); // very fast first full scan comparison

Set<String> changedNames = result.stream()
     .map(outer -> collectionB.stream()
                               // very slow second full scan comparison
                              .filter(inner -> inner.getKeyA().equals(outer.getKeyA()) 
                                           && inner.getKeyB().equals(outer.getKeyB()) 
                                           && inner.getKeyC().equals(outer.getKeyC()))
                              .findFirst()
                              .map(item -> item.getName()))
     .collect(toSet());
log.info("changed names" + changedNames);

Guava Sets.differences 可以在不到 1/10 秒的时间内找到大于 300k 的集合的差异,但后来我仍然进行全面扫描以收集名称。

我只是在猜测,但是有没有像

Set<B> result = Sets.differences(setA, setB, a -> a.customHashCode(), b -> b.customHashCode(), (a, b) -> a.customEquals(b))

使用自定义 hashCode 和自定义 equals 方法来保持 Sets 的效率,或者有一些更好的模式来进行这种比较,因为我认为这似乎是常见问题?

编辑 我刚刚发现我可以将转换翻转为扩展 class

toB() {
  return new B(keyA, keyB, keyC, null, null);
}

但我需要重写 hashCode 和 equals 以仅使用这 3 个字段,我仍然相信有更优雅的方法

我们可以流式传输第一个集合,对于每个 A 对象,用分隔符连接 A 的三个字段并将其收集为一个集合 (Set<String>)。

然后我们遍历第二个集合的元素,根据A的关键字段组成一个字符串,并检查上面计算的集合是否有。

Set<String> keysOfA = collectionA.stream()
        .map(a -> compose(a.getKeyA(), a.getKeyB(), a.getKeyC()))
        .collect(Collectors.toSet());

Set<String> changedNames = collectionB.stream()
        .filter(b -> !keysOfA.contains(compose(b.getKeyA(), b.getKeyB(), b.getKeyC())))
        .map(b -> b.getName())
        .collect(Collectors.toSet());

static String compose(String keyA, String keyB, String keyC) {
    return keyA + "|" + keyB + "|" + keyC; //any other delimiter would work
}

有了这个你就不需要 toA() 方法了。


第二种方法:

如果class A实现了equals和hashcode,那么你可以这样做

Set<String> changedNames = collectionB.stream()
        .filter(b -> !collectionA.contains(b.toA()))
        .map(b -> b.getName())
        .collect(Collectors.toSet());

这是 O(n^2),因为您正在为结果中的每个元素流式传输 collectionB。以下应该工作得非常快:

Set<String> changedNames = collectionB.stream()
                              .filter(b -> collectionA.contains(b.toA())
                              .map(item -> item.getName()).collect(toSet());