什么是解析包含多个单词组合的字符串的最佳方法?

4

我正在编写一个程序,试图从自然语言中获取意义。该程序将接受一个字符串,并查看它是否包含特定的单词组合。请参见以下代码片段作为示例:

if (phrase.contains("turn")) { // turn something on/off
    if (phrase.contains("on") && !phrase.contains("off")) { // turn something ON
        if (phrase.contains("pc") || phrase.contains("computer")) // turn on computer
            turnOnComputer();
        else if (phrase.contains("light") || phrase.contains("lamp")) // turn on lights
            turnOnLights();
        else
            badPhrase();
    }
    else if (phrase.contains("off") && !phrase.contains("on")) { // turn something OFF
        if (phrase.contains("pc") || phrase.contains("computer")) // turn off computer
            turnOffComputer();
        else if (phrase.contains("light") || phrase.contains("lamp")) // turn off lights
            turnOffLights();
        else
            badPhrase();
    }
    else {
        badPhrase();
    }
}
else {
    badPhrase();
}

如您所见,如果我想解释多个含义,这很快就会变成一堆无法管理的代码混乱。我该如何更好地管理它?


@MarounMaroun 感谢指出。这本不应该是这样的,但我不想花时间用更多的 if 语句来修复它。 - BLuFeNiX
不会的。抱歉,那是我的错误。 - Maroun
@MarounMaroun 嗯,它确实有类似的问题。 - BLuFeNiX
5个回答

3

Apache OpenNLP是一款基于机器学习的自然语言文本处理工具包。

它包括一个句子检测器,一个分词器,一个词性标注器和一个树库解析器

NLP手册

下载

希望这能有所帮助 ;)


2

关键词检测只能适用于非常少量的单词和/或非常受限的输入语言,如果周围文本不重要的话也可以使用。

但是,对于这种自然语言解析,您需要采用更复杂的方法,例如首先对文本进行标记化,然后尝试找到单词之间的语法关系(从直接邻居开始并逐渐扩大范围)。最后将您发现的语法关系用作控制码来驱动您的操作决策。

正则表达式可能不是这里的答案,因为它们需要非常严格的输入。考虑以下句子:

不要把灯关掉,而是把它打开。

无论是正则表达式还是您的原始方法都无法给出任何明智的结果。此外,请勿忘记语法或语法错误。


我很想能够支持这样的句子,但这似乎是一个宏伟的目标。我不知道该从哪里开始。你的解释很有道理,但实际实现起来却像一场噩梦。 - BLuFeNiX
我的例子是针对纯自然语言的。如果您可以限制输入语言(例如,仅允许一定的关键词和修饰符),那么这将使任务变得更加容易。但即使如此,您仍然面临着各种需要理解的自由文本。也许一个简单的编程语言(领域特定语言)就足够了?这样就可以使用普通解析器来处理它,从而使您的任务变得更加简单。 - Mike Lischke

1

首先,我不确定您的方法在自然语言处理方面是否适用。此外,是否已经存在用于NLP的库?特别是在NLP中,我知道有时顺序和词性非常重要,而且这种方法对单词变化不太稳健。

然而,如果您想坚持使用您的方法,使其更易读和更易维护(详见下文的优缺点),可以考虑以下方法:

StringFinder finder = new StringFinder(phrase);
if        (finder.containsAll("turn", "on").andOneOf("computer", "pc").andNot("off").matches()) {
    turnOnComputer();
    return;
} else if (finder.containsAll("turn", "off").andOneOf("computer", "pc").andNot("on").matches()) {
    turnOffComputer();
    return;
} else if (finder.containsAll("turn", "on").andOneOf("light", "lamp").andNot("off").matches()) {
    ...
} else if (finder.containsAll("turn")) { // If we reached this point
    badPhrase();
} else if (...

类似于以下内容:

class StringFinder {
    private final String phrase;
    private final Map<String, Boolean> cache = new HashMap<String, Boolean>();

    public StringFinder(String phrase) { this.phrase = phrase; }

    public StringFinder containsAll(String... strings) {
        for (String string : strings) {
            if (contains(string) == false) return new FailedStringFinder(phrase);
        }
        return this;
    }

    public StringFinder andOneOf(String... strings) {
        for (String string: strings) {
            if (contains(string)) return this;
        }
        return FailedStringFinder(phrase);
    }

    public StringFinder andNot(String... strings) {
        for (String string : strings) {
            if (contains(string)) return new FailedStringFinder(phrase);
        }
        return this;
    }

    public boolean matches() { return true; }

    private boolean contains(String s) {
        Boolean cached = cache.get(s);
        if (cached == null) {
            cached = phrase.contains(s);
            cached.put(s, cached);
        }
        return cached;
    }


}

class FailedStringFinder extends StringFinder {
    public boolean matches() { return false; }

    // The below are actually optional, but save on performance:
    public StringFinder containsAll(String... strings) { return this; }
    public StringFinder andOneOf(String... strings) { return this; }
    public StringFinder andNot(String... strings) { return this; }
}

缺点:

  • 检查的重复性: "turn" 被多次检查。
  • 重复的模式(但请参见下面的优点)。

优点:

  • 代码相对简洁。
  • 检查被重复但被缓存,因此性能保持高水平。
  • 条件非常接近操作,导致代码非常易读。
  • 不嵌套条件允许更改特定操作所需的条件而无需重构代码,从而导致更可维护的代码。
  • 可以轻松更改条件和操作出现的顺序,以控制优先级。
  • 缺乏嵌套使其更容易在将来并行化。
  • 灵活的条件检查:例如,您可以添加方法到StringFinder以匹配重复检查,例如:public StringFinder containsOnAndNotOff() { return containsAll("on").andNot("off"); },或者匹配您需要的一些奇异条件,例如andAtLeast3Of(String... strings) {...}
    • 缓存也可以扩展到不仅记住单词是否出现,还要记住整个模式是否出现。
    • 您还可以添加最终条件:andMatches(Pattern p)(使用正则表达式模式)-实际上,您可以用正则表达式模型化许多其他检查。然后,它将很容易进行缓存-使用模式而不是字符串作为键。

哇,谢谢!这个更容易阅读,也更容易编辑。 - BLuFeNiX
提醒:您的代码存在一些错误,因此我已发布了一个包含更正代码的答案。这将是我的采纳答案。 - BLuFeNiX
@BLuFeNiX 这就是在文本编辑器中编写代码的结果 :) 感谢您的更正! - Oak

1
使用正则表达式可以实现你想要的功能,因为正则表达式可以匹配字符串的组合。

经过一番谷歌搜索,我认为我需要这样的东西:if (phrase.matches("^(?=.*?(light|lamp))(?=.*?(turn))(?=.*?(on))((?!off).)*$")) { turnOnLights(); }有没有简化这个语法的方法? - BLuFeNiX
如果你有复杂的逻辑,那么正则表达式也会变得复杂。我不是一个正则表达式专家,所以无法建议是否可以简化这个正则表达式。 - Bhushan Bhangale

0

这是由@Oak提供的答案中的修正代码

import java.util.HashMap;
import java.util.Map;

class StringFinder {
    private final String phrase;
    private final Map<String, Boolean> cache = new HashMap<String, Boolean>();

    public StringFinder(String phrase) { this.phrase = phrase; }

    public StringFinder containsAll(String... strings) {
        for (String string : strings) {
            if (contains(string) == false) return new FailedStringFinder(phrase);
        }
        return this;
    }

    public StringFinder andOneOf(String... strings) {
        for (String string: strings) {
            if (contains(string)) return this;
        }
        return new FailedStringFinder(phrase);
    }

    public StringFinder andNot(String... strings) {
        for (String string : strings) {
            if (contains(string)) return new FailedStringFinder(phrase);
        }
        return this;
    }

    public boolean matches() { return true; }

    private boolean contains(String s) {
        Boolean cached = cache.get(s);
        if (cached == null) {
            cached = phrase.contains(s);
            cache.put(s, cached);
        }
        return cached;
    }


}

class FailedStringFinder extends StringFinder {

    public FailedStringFinder(String phrase) {
        super(phrase);
    }

    public boolean matches() { return false; }

    // The below are actually optional, but save on performance:
    public StringFinder containsAll(String... strings) { return this; }
    public StringFinder andOneOf(String... strings) { return this; }
    public StringFinder andNot(String... strings) { return this; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接