2013-05-08 77 views
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(); 
} 

正如你所看到的,这样可以迅速成为代码不可收拾的烂摊子,如果我要解释以上几个含义更多。我怎样才能更好地管理这个?

+0

@MarounMaroun感谢指出了这一点提供了答案的固定代码。它不应该,但我不想花时间用更多的if语句来解决这个问题。 – BLuFeNiX 2013-05-08 06:38:15

+0

它不会。对不起,那是我的错。 – Maroun 2013-05-08 06:38:50

+0

@MarounMaroun那么,它肯定有类似的问题。 – BLuFeNiX 2013-05-08 06:40:07

回答

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; } 
} 

缺点:

  • 重复检查: “转” 检查多次。
  • 重复模式(但看到下面的优点)。

优点:

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

哇,谢谢!这是更容易阅读,并易于编辑。 – BLuFeNiX 2013-05-08 16:21:51

+0

供参考:您的代码有一些错误,所以我发布了一个包含更正的代码的答案。这仍然是我接受的答案。 – BLuFeNiX 2013-05-09 19:28:16

+0

@BLuFeNiX这就是您在文本编辑器中编写代码时所得到的结果:)感谢您的更正! – Oak 2013-05-09 20:03:00

1

使用正则表达式来实现你想要的正则表达式作为可以匹配字符串的组合。

+0

经过一些谷歌搜索后,我想我想要这样的东西:'if(phrase.matches(“^(?=。*?(light | lamp))(?=。*?(turn))(?=。*? (on))((?! off)。)* $“)){turnOnLights(); }' 有没有简化这种语法的方法? – BLuFeNiX 2013-05-08 06:50:02

+0

如果你有复杂的逻辑,那么正则表达式将是复杂的。我不是一个正则表达式专家,所以不能建议这个正则表达式是否可以变得更简单。 – 2013-05-08 07:47:46

2

关键词定位肯定是只为一个非常小的组字和/或非常有限的输入语言manageble。好吧,也许如果周围的文字并不重要。

但是对于那种自然语言解析的你需要一个更复杂的方法像第一令牌化的文本,然后试图找出字之间句法关系(与直接邻居开始,后来扩大范围)。最后,使用您发现的语法关系作为控制代码来推动您的行动决策。

正则表达式可能不是问题的答案在这里,因为他们需要一个非常严格的输入。考虑这样的句子:

请勿关闭电源,而应将其打开。

RE和你原来的方法都不会给你任何明智的结果。另外,不要忘记句法或语法错误。

+0

我希望能够支持这样的句子,但它似乎是一个崇高的目标。我不知道从哪里开始。你的解释是有道理的,但实际执行它似乎是一场噩梦。 – BLuFeNiX 2013-05-08 16:17:24

+0

我的例子是纯粹的自然语言。如果您可以限制输入语言(例如仅允许一组特定的关键字和修饰符),它会使任务变得更容易。但即使如此,你仍然面临着你必须理解的各种自由文本。也许一个简单的编程语言(领域特定语言)就足够了?这可以由一个普通的解析器来处理,而这会使你的任务变得更简单。 – 2013-05-08 16:46:44

3

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

它包括一个句子检测器,一个标记生成器,一个份的词性(POS)标注器树库解析器

Manual for NLP

Download

希望它能帮助; )

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; } 
} 
相关问题