You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何降低大规模语料双词提取程序的时间复杂度?

大规模语料双词(Bigram)提取的性能优化方案

问题背景

需要处理平均50万行的大规模语料,提取每个句子中的双词组合,但当前实现采用三层嵌套循环,时间复杂度达O(n³),运行效率极低,急需优化思路。

当前实现代码

主循环代码

while (count < fileStream.size()) { // 1st loop

    List<String> lineArray = new ArrayList<>(Arrays.asList(fileStream.get(count).split("\\$+")));
    //lineArray.forEach(System.out::println);

    if (lineArray.size() > 2) {

        List<Sentence> sentences = sentenceSplitter.split(shoppingPlatform.getProductDescription());
        sentences.replaceAll(sentence -> new Sentence(sentence.toString().toLowerCase(new Locale("tr-TR"))));

        for (Sentence sentence : sentences) { // 2nd loop

            FsmParseList[] parseLists = fsm.robustMorphologicalAnalysis(sentence);
            ArrayList<FsmParse> candidateParses = morphologicalDisambiguator.disambiguate(parseLists);

            map = biGramSentences(map, map2, shortcuts, unicodes, candidateParses);

        }
        //sentences.forEach(System.out::println);
    }
    count++;
}

双词提取核心方法

private static Map<String, Integer> biGramSentences(Map<String, Integer> map, Map<String, String> map2, ArrayList<String> shortcuts, ArrayList<String> unicodes, ArrayList<FsmParse> candidateParses) {

    Set<String> keys = map2.keySet();
    for (int i = 0; i < candidateParses.size() - 1; i++) { // 3rd loop

        for (String key : keys) {

            if (candidateParses.get(i).getSurfaceForm().equals(key)) {
                ...
            }
            if (candidateParses.get(i + 1).getSurfaceForm().equals(key)) {
                ...
            }
        }

        String temp = candidateParses.get(i).getSurfaceForm() + " " + candidateParses.get(i + 1).getSurfaceForm();

        if (map.get(temp) != null) {
            map.put(temp, map.get(temp) + 1);
        } else if (map.get(temp) == null) {

            for (String unicode : unicodes) {

                if (candidateParses.get(i).getSurfaceForm().contains(unicode)) {
                   ...
                }
                if (candidateParses.get(i + 1).getSurfaceForm().contains(unicode)) {
                   ...
                }
            }

            boolean flag2 = true;
            for (String shortcut : shortcuts) {

                if (candidateParses.get(i).getSurfaceForm().equals(shortcut)
                        || candidateParses.get(i + 1).getSurfaceForm().equals(shortcut)) {

                    ...
                }
            }

            if (flag2 && (Objects.equals(candidateParses.get(i).getFinalPos(), "VERB") || Objects.equals(candidateParses.get(i + 1).getFinalPos(), "VERB")
                    || Objects.equals(candidateParses.get(i).getFinalPos(), "NUM") || Objects.equals(candidateParses.get(i + 1).getFinalPos(), "NUM")
                    || Objects.equals(candidateParses.get(i).getPos(), "PUNC") || Objects.equals(candidateParses.get(i + 1).getPos(), "PUNC"))) {

                ...
            }

            if (flag2 && (candidateParses.get(i).isPunctuation() || candidateParses.get(i + 1).isPunctuation()
                    || candidateParses.get(i).isNumber() || candidateParses.get(i + 1).isNumber())) {

                ...
            }

            if (flag2)
                map.put(temp, 1);
        }
    }
    return map;
}

优化思路

1. 用HashSet消除嵌套循环,将线性查询转为O(1)操作

当前biGramSentences中,遍历map2.keySet()和shortcuts的嵌套循环是主要耗时点。提前将这两个集合转为HashSet,直接通过contains方法判断元素是否存在,彻底消除嵌套循环:

// 预初始化(仅执行一次)
Set<String> map2KeySet = new HashSet<>(map2.keySet());
Set<String> shortcutSet = new HashSet<>(shortcuts);

// 在biGramSentences中替换原循环逻辑
if (map2KeySet.contains(current.getSurfaceForm())) {
    ...
}
if (map2KeySet.contains(next.getSurfaceForm())) {
    ...
}

// 替换shortcut的遍历判断
if (shortcutSet.contains(current.getSurfaceForm()) || shortcutSet.contains(next.getSurfaceForm())) {
    ...
}

2. 优化HashMap计数逻辑,减少重复哈希查询

原代码中判断map.get(temp)是否存在再更新计数,需要两次哈希查询。改用Map.merge方法,一次操作完成计数更新,效率更高:

// 替换原计数逻辑
map.merge(temp, 1, Integer::sum);

若需保留过滤逻辑,可先通过map.containsKey(temp)判断,仅当双词不存在时才执行过滤,避免无效计算。

3. 减少重复对象访问与创建

  • 在biGramSentences的循环中,多次调用candidateParses.get(i)和get(i+1),提前将对象存入局部变量,避免重复索引查找:
for (int i = 0; i < candidateParses.size() - 1; i++) {
    FsmParse current = candidateParses.get(i);
    FsmParse next = candidateParses.get(i+1);
    // 后续操作直接使用current和next
}
  • 句子转小写时,若Sentence类支持直接修改内部文本,避免新建对象:
sentences.replaceAll(sentence -> {
    sentence.setText(sentence.toString().toLowerCase(new Locale("tr-TR")));
    return sentence;
});

4. 并行化处理独立任务

语料的行、句子之间处理逻辑完全独立,可使用并行流加速。注意将非线程安全的HashMap替换为ConcurrentHashMap:

fileStream.parallelStream().forEach(line -> {
    List<String> lineArray = new ArrayList<>(Arrays.asList(line.split("\\$+")));
    if (lineArray.size() > 2) {
        List<Sentence> sentences = sentenceSplitter.split(shoppingPlatform.getProductDescription());
        sentences.replaceAll(...);
        for (Sentence sentence : sentences) {
            // 形态分析与双词提取逻辑
            ...
            concurrentMap.merge(temp, 1, Integer::sum);
        }
    }
});

5. 提前过滤无效数据

进入形态分析等耗时步骤前,过滤掉空句子、长度不足2的句子(无法生成双词),减少不必要的计算:

for (Sentence sentence : sentences) {
    if (sentence.length() < 2) {
        continue;
    }
    // 后续形态分析与双词提取
    ...
}

6. 优化Unicode检查逻辑

若unicode集合较大,预编译正则表达式,一次性检查是否包含任意unicode字符,替代遍历判断:

// 预初始化(仅执行一次)
String unicodePattern = String.join("|", unicodes);
Pattern pattern = Pattern.compile(unicodePattern);

// 在biGramSentences中使用
Matcher matcher = pattern.matcher(current.getSurfaceForm());
if (matcher.find()) {
    ...
}
matcher.reset(next.getSurfaceForm());
if (matcher.find()) {
    ...
}

内容的提问来源于stack exchange,提问作者Abdullah Gür

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 13:55:28