在大型文本文件中查找模式出现(当前使用Aho-Corasick)

时间:2018-09-25 16:23:21

标签: java search pattern-matching trie aho-corasick

我有一个大文本(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;

谢谢!

我也尝试了实现中可用的非重叠选项,但是它不符合要求,使用起来也很慢。

1 个答案:

答案 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方法。