我有2个ArrayList' s。 ArrayList A
有8.1k个元素,ArrayList B
有81k个元素。
我需要遍历B
,在A
中搜索该特定项目,然后更改列表B
中匹配元素中的字段。
这是我的代码:
private void mapAtoB(List<A> aList, ListIterator<B> it) {
AtomicInteger i = new AtomicInteger(-1);
while(it.hasNext()) {
System.out.print(i.incrementAndGet() + ", ");
B b = it.next();
aList.stream().filter(a -> b.equalsB(a)).forEach(a -> {
b.setId(String.valueOf(a.getRedirectId()));
it.set(b);
});
}
System.out.println();
}
public class B {
public boolean equalsB(A a) {
if (a == null) return false;
if (this.getFullURL().contains(a.getFirstName())) return true;
return false;
}
}
但这是永远的。要完成此方法,需要将近15分钟。有没有办法优化任何这个? 15分钟的运行时间太长了。
答案 0 :(得分:1)
我会很高兴看到一个良好而彻底的解决方案,同时我可以提出两个想法(或者两个转世一个)。
第一个是加速在类型B的一个对象中搜索类型A的所有对象。为此,Rabin-Karp算法似乎适用且简单到足以快速实现,Aho-Corasick更难但是可能会给出更好的结果,不确定会有多好。
另一个选项是限制B类型的对象数量,对于A的每个对象,应该对其进行完全处理。构建一个逆N-gram索引:对于每个fullUrl,你取其长度为N的所有子串(“N-grams”),然后从每个这样的N-gram构建一个映射到一组具有这种N-gram的B克在他们的fullUrl。当搜索一个对象A时,你获取它的所有N-gram,为每个这样的N-gram找到一组B并且与所有这些集相交,交集将包含你应该完全处理的所有B。我快速实现了这种方法,对于你指定的大小,它为N = 4提供了6-7的时间加速;随着N的增长,搜索变得更快,但构建索引会变慢(所以如果你可以重用它,你可能最好选择更大的N)。对于您指定的大小,此索引大约需要200 Mb,因此这种方法只会随着B集合的增长而扩展。假设所有字符串都长于NGRAM_LENGTH
,这里是使用Guava的SetMultimap,HashMultimap构建索引的快速而脏的代码:
SetMultimap<String, B> idx = HashMultimap.create();
for (B b : bList) {
for (int i = 0; i < b.getFullURL().length() - NGRAM_LENGTH + 1; i++) {
idx.put(b.getFullURL().substring(i, i + NGRAM_LENGTH), b);
}
}
对于搜索:
private void mapAtoB(List<A> aList, SetMultimap<String, B> mmap) {
for (A a : aList) {
Collection<B> possible = null;
for (int i = 0; i < a.getFirstName().length() - NGRAM_LENGTH + 1; i++) {
String ngram = a.getFirstName().substring(i, i + NGRAM_LENGTH);
Set<B> forNgram = mmap.get(ngram);
if (possible == null) {
possible = new ArrayList<>(forNgram);
} else {
possible.retainAll(forNgram);
}
if (possible.size() < 20) { // it's ok to scan through 20
break;
}
}
for (B b : possible) {
if (b.equalsB(a)) {
b.setId(a.getRedirectId());
}
}
}
}
优化的一个可能方向是使用散列而不是完整的N-gram,从而减少内存占用和N-gram密钥比较的必要性。