我有一个大文本(5MB-500MB)文件和一组几千个模式。对于每个模式,我想获取文件中该模式的出现次数。文本不包含空格,是基本的长字母数字字符串。
为此,我试图使用Aho-Corasick算法, 特别是Robert-Bor的Java实现,它确实足够快地运行,但是有一个问题:用模式计数发射,因为它们的字符串不等于使用文本编辑器(例如notepad ++)打开文本文件的结果并计算模式。对我来说重要的是,计数的出现次数将恰好是在文件中找到模式的次数。因此,我需要找到解决该问题的方法。
为了实现我的目标,我可以对算法的实现进行一些更改吗?也许某种EmitHandler可以解决我的问题? 我也乐于接受其他建议,例如替换算法/求解方法。但是,如果可能的话,我希望继续使用Java,并尽可能快地获得结果(例如,发射索引对我而言并不重要)。
编辑:例如,即使是安装文件的一小段以下文字: File Link,以及模式:
根据发射计数的5b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e5ff55b4e
在文件中出现150次,但是根据浏览器中Notepad ++ / Ctrl-f的计数功能,它仅出现10次。
以及同一文本上的另一个示例:
f34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a6e0ff34a
根据发射计数出现99次,但根据文本编辑器计数仅出现10次。
链接到算法的实现here。 我当前根据实现运行的内容:
Trie trie = Trie.builder().addKeywords(wordsMap.keySet())
.build();
Collection<Emit> ls2 = trie.parseText(str);``
for (Emit e: ls2) {
if (!map.containsKey(e.getKeyword()))
map.put(e.getKeyword(),1);
else {
int val = map.get(e.getKeyword());
map.replace(e.getKeyword(),val+1);
}
}
return map;
谢谢!
我也尝试了实现中可用的非重叠选项,但是它不符合要求,使用起来也很慢。
答案 0 :(得分:2)
首先,尚不清楚使用Trie
构建ignoreOverlaps()
时该算法如何或为什么不适合您的正确性要求。我同意你的意思。当您说在这种情况下会对性能产生影响时,我也准备相信您。
因此,与其深入研究算法的实现,不如将其与重叠部分一起使用,然后手动删除重叠部分。在这种情况下,我认为您可以微调发出的声音。
以下是初始化Trie
的代码:
String text = ... // read the text somewhere
Set<String> keywords = new HashSet<>();
keywords.add("keyword1");
keywords.add("keyword2");
Trie trie = Trie.builder().addKeywords(keywords).build(); // with overlaps!
现在,让我们来分析文本:
Collection<Emit> parseResults = trie.parseText(text);
据我所知,解析结果是按文本中出现的顺序返回的,但我尚未对此进行全面测试。为了使以下代码正常工作,我们需要按起始索引对发射进行排序。
鉴于发射按开始索引排序,下面是用于计算每个关键字不重叠发射的代码:
Map<String, Long> map = parseResults.stream()
.collect(Collectors.groupingBy(Emit::getKeyword, countingNonOverlaps()));
countingNonOverlaps()
实用程序方法如下:
private static Collector<Emit, ?, Long> countingNonOverlaps() {
class Acc {
Emit last;
long count = 0;
void add(Emit e) {
if (last == null || !last.overlapsWith(e)) { count++; last = e; }
}
Acc merge(Acc another) {
throw new UnsupportedOperationException("Parallel not supported");
}
}
return Collector.of(Acc::new, Acc::add, Acc::merge, acc -> acc.count);
}
此方法使用自定义收集器来计算每个关键字的不重叠发射。没有自定义收集器,还有其他更简单的方法可以执行此操作,但是它们需要保留每个关键字的不重叠发射的列表。因为您只需要计数,并且您正在使用2000个关键字和一个很大的文本,所以我认为这种方法更好。
收集器基本上跟踪收集到的最后一个非重叠发射,并且仅当收集的当前发射与最后一个非重叠发射不重叠时,才增加计数。此外,它仅适用于顺序流。
注意:如果需要在增加计数器时进行微调,则可以自定义add
本地类的Acc
方法。