比较两组不同类型的有效方法
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());
首先,我需要一些非常有效的解决方案,因为我正在比较具有 >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());