【问题标题】:Efficient way to compare two sets of different type比较两组不同类型的有效方法
【发布时间】:2020-10-12 20:00:30
【问题描述】:

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

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

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);
   }
}

它们都包含几个组成键的字段(在此示例中,三列键 = keyA keyB keyC)

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

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 的 Sets 的差异,但后来我仍然进行全面扫描以收集名称。

我只是猜测,但有没有类似的东西

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

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

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

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

但是我需要覆盖 hashCode 和 equals 以仅使用这 3 个字段,我仍然相信有更优雅的方式

【问题讨论】:

  • 您的B.toA() 没有投射 B 实例。相反,它创建了一个全新的A,其属性值取自B。如果可以在这些类型之间进行转换(它不是),那么这比转换成本要高得多。尽管如此,这可能是一个合理的前进方向,但请确保您了解这里实际发生的情况。
  • 确实,但我只是用错了词。我编辑了帖子,谢谢。
  • 我已将collectionBConvertedToA的类型编辑为Set&lt;A&gt;

标签: java algorithm performance guava


【解决方案1】:

我们可以流式传输第一个集合,并为每个 A 对象,通过分隔符连接 A 的三个字段并将其收集为一个集合 (Set&lt;String&gt;)。

然后我们遍历第二个集合的元素,根据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() 方法了。


第二种方法:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 我不敢相信它是如此简单 -.- 我会等待更多的回应,但我认为没有比这更优雅的了,谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 1970-01-01
    • 2016-10-14
    • 2021-04-29
    • 1970-01-01
    • 1970-01-01
    • 2018-04-24
    相关资源
    最近更新 更多