非常基本的英语语法分析器

时间:2009-06-23 16:17:01

标签: c++ parsing nlp

我正在编写一个非常基本的解析器(主要是为了更好地理解它们是如何工作的),它使用户输入选择的几个单词,检测句子结构是否正常,并输出结果。语法是:

句: Noun Verb

文章句子

句子连词句

连词: “和” “要么” “而是”

名词: “鸟类” “鱼” “C ++”

动词: “规则” “飞” “游”

文章: “该”

编写语法很简单。它正在实现给我带来麻烦的代码。我的伪代码是:

main()
get user input (string words;)
while loop (cin >> words)
call sentence()
end main()

sentence()
call noun()
if noun() call verb() (if verb is true return "OK" ???)(else "not ok"???)
else if not noun() call article()
                if article() call sentence() (if sentence is true "OK"???)(else "not"?)
else if not noun() call conjunction()
                   if sentence() conjunction() sentence() - no idea how to implement
                                                             return "OK"
else "not ok"

所以我的代码非常邋ps。我有一些关于实施它的问题。

  1. 对于单词函数(名词,动词等),我该如何检查它们是否属实? (如检查用户的输入是否有鸟类,鱼类,苍蝇,游泳等)

  2. 我应该如何处理联合召唤和输出?

  3. 我应该处理主函数或调用函数的输出吗?

  4. 如果我的伪代码完全错误,上述问题都不重要。基础知识有什么问题吗?

  5. 作为补充说明,我正在进行第6章编程练习:使用C ++实践和原理,所以我更喜欢使用我已经学过的语言语法,所以任何属于高级编程的类别可能不是很有帮助。 (该练习明确表示不使用令牌,因此请将其计算在内。)

    提前致谢

    最后编辑:在本书的公共小组中,我问了同样的问题,Bjarne Stroustrup回复说他把运动解决方案放在网上。他基本上把输入读入了句子函数,并使用if语句返回true或false。但是,他没有使用文章,所以我的文章要复杂得多。我想如果我从这个练习中学到了什么,那么在处理大量用户输入时,标记化是关键(从目前为止我所知道的。)这是我现在的代码。我可能会稍后回到它,因为它仍然是非常错误的,并且基本上只有在句子没问题且不能处理(名词,连词,句子)之类的情况下才会返回,但是现在我正在继续。

    #include "std_lib_facilities.h"
    
    bool article(string words)
    {
                   if (words == "the")
                   return true;
                   else return false;        
    }
    
    bool verb(string words)
    {
                   if (words == "rules" || words == "fly" || words == "swim")
                   return true;
                   else return false;                   
    }
    
    bool noun(string words)
    {
                   if (words == "birds" || words == "fish" || words == "c++")
                   return true;
                   else return false;                   
    }
    
    bool conjunction(string words)
    {
                  if (words == "and" || words == "but" || words == "or")
                  return true;
                  else return false;                  
    }
    
    bool sentence()
    {
    string w1;
    string w2;
    string w3;
    string w4;
    
    cin >> w1;
    if (!noun(w1) && !article(w1)) return false; // grammar of IFS!
    
    cin >> w2;
    if (noun(w1) && !verb(w2)) return false;
    if (article(w1) && !noun(w2)) return false;
    
    cin >> w3;
    if (noun(w1) && verb(w2) && (w3 == ".")) return true;
    if (verb(w2) && !conjunction(w3)) return false;
    if (noun(w2) && !verb(w3)) return false;
    if (conjunction(w3)) return sentence();
    
    cin >> w4;
    if (article(w1) && noun(w2) && verb(w3) && (w4 == ".")) return true;
    if (!conjunction(w4)) return false;
    if (conjunction(w4)) return sentence();
    }
    
    
    int main()
    {                                   
    cout << "Enter sentence. Use space then period to end.\n";
                bool test = sentence();
                if (test)
                   cout << "OK\n";
                else
                   cout << "not OK\n";
    

    keep_window_open();     }

9 个答案:

答案 0 :(得分:9)

确定。如果你真的想亲手做到: - (

这个问题分为两部分:

  • 词法分析
  • 句法分析。
  • 我们可以忽略语义分析,因为这就是为什么。

首先,您已将输入流标记为合理的令牌。单词将是一个obvios选择,但这将为句法阶段留下很多工作。所以我会把你的单词分成以下几种类型(Conjunction,Noun,Verb,Article),然后写一个词法分析器来返回正确的Lexems。

Lexer.cpp
enum Lexeme { END,Conjunction,Noun,Verb,Article };
Lexem getNextLexme(std::istream in)
{
    std::string word;
    in >> word;

    if (!in) {return END;}

         if (word == "and")   return Conjunction;
    else if (word == "birds") return Noun;
    else if (word == "fly")   return Verb;
    else if (word == "the")   return Article;

    ... etc
}

所以现在你可以根据简化的令牌流来解决你的语法分析器。

bool ParseSentence(std::istream in)
{
    Lexeme token = getNextLexme(in);
    switch(token)
    {
        case Noun:    if (!parseVerb(in))
                      {    return false;
                      }
                      return parseConjunctionOrEnd(in);
        case Article: return ParseSentence();
        case END:     return true;
    }
}
bool parseVerb(std::istream in)
{
    Lexeme token = getNextLexeme(in);
    if (token != Verb) { /*ERROR*/ return false;}
    return true;
 }
 // etc

语法分析的另一个选择是构建状态表。但这涉及手工分析语法和确定状态。这应该只用最简单的语法来处理,任何比这里大的东西都应该留给可以自动生成状态表的工具。

假设我在下面的原始帖子中定义的语法:
并希望我得到它正确,因为我不是一个可以使用的工具: - )

State 1:   Start <Nothing Happened>
               Article -> State 2
               Noun -> State 3
               Otherwise Error
State 2:   Seen Article.
               Noun -> State 3
               Otherwise Error
State 3:   Seen Noun  in Sentence.
               Verb -> State 4
               Otherwise Error
State 4:   Seen Noun Verb
               End -> State 5
               Conjunction -> State 1
State 5:   Finished:

State 0:   Error State.


int stateTable[][]    // CurrentState,CurrentObject
           = {/*State 0: Error State:*/{},
                                       // END,Conjunction,Noun,Verb,Article 
              /*State 1: Start*/       {  0,  0,          3,   0,   2},
              /*State 2: Article*/     {  0,  0,          3,   0,   0},
              /*State 3: Noun*/        {  0,  0,          0,   4,   0},
              /*State 4: Noun Verb*/   {  5,  1,          0,   0,   0},
              /*State 5: End*/         {}
             };

bool parseSentence(std::iostream& in)
{
    int currentState = 1;
    while((currentState != 0) && (currentState != 5))
    {
        int token = getNextLexme(in);
        currentState = stateTable[currentState][token];
    }
    return currentState == 5;
}

答案 1 :(得分:5)

我对这个问题很感兴趣。我要帮助OP,Alex,做些什么,但由于我不太了解C ++,它将使用伪C ++。它也不会使用lex / yacc,因为Alex想要了解它们是如何制作的。如果你使用它们,lex和yacc等工具就会成为“黑盒子”。我现在没有时间把它们放在一起,但我可以在几个小时内一块一块地完成它。我只是想现在开始。

我们需要做的第一件事就是清理语法。你有一个这样定义的句子:

sentence : noun verb
         | article sentence
         | sentence conjunction sentence

这个语法有缺陷。诸如“游泳鱼”之类的句子是有效的,因为句子是根据其自身来定义的。递归是语法的正常部分,但需要正确处理。我打算猜测你不希望连续出现两篇或多篇文章。

更好的句子语法可以是:

sentence : clause conjunction clause
         | clause

clause : nounphrase verbphrase

nounphrase : noun
           | article noun

这将删除可能导致无限循环的无约束递归。

现在我们已经准备好解决解析器本身了。由于这是C ++,我们不妨使它面向对象。我现在必须用scoot,但我会给你一个提示:每个生产规则都会有一个类。

答案 2 :(得分:2)

好的......我不会回答你的具体问题,但我想指出一些在讨论这个问题时要考虑的一般性想法。首先,解析很难。你有一个简单的语法,但它仍然很难。编译器前端负责解析...只是为了给出一些上下文。

有两种基本类型的解析...自顶向下解析和自底向上解析。它们以它们如何遍历语法树命名(想想将为可能的构造创建什么类型的语法树)。自上而下解析很容易,并且可能适用于您想要做的事情。最常见的自顶向下解析方法是递归下降解析:http://en.wikipedia.org/wiki/Recursive_descent_parser

但是,要使用递归下降解析,您的语法必须采用某种格式...对于某些语法,递归下降是不可能解析它们的。你应该能够改革你的语法以适应这一点。

自上而下的解析器很容易编写...因为您只需要一些小语言的函数。

解析的第二种方法是自下而上解析。这通常用在编译器中,因为它没有自上而下的限制。如果给定的字符串不适合语言,则更容易进行错误报告。

自下而上的解析器难以编写...大多数人使用解析器生成器来完成工作。我和YACC一起工作了很多。您基本上输入语法(以及匹配规则时要采取的操作)并解析语法。

自下而上的解析器使用称为shift-reduce解析的东西。这是处理输入和匹配规则的方法。

再次查看您的代码,我会说您可以使用自上而下的解析器。对不起,我不能给出具体的代码,但谷歌的自上而下的解析器示例(或递归下降解析器示例)可能会出现您需要的代码。

答案 3 :(得分:1)

大多数解析程序文本解析都是使用正式的语法解析器完成的。英语和大多数口语语言都不是正式的语法,你将很难解析它们。非常。这个问题已经将PHD捆绑了几十年而没有取得多大成功。

答案 4 :(得分:1)

词性

要获得词性,您需要a dictionary list with parts of speech。除了哈希表将单词映射到词性列表之外,检查语音部分的另一种可能方法是将每个词性的每组单词加载到其自己的Bloom filter中(一种压缩的哈希值)从字符串映射到布尔值。)

答案 5 :(得分:1)

自然语言的语法的一个方面是它们通常是暧昧的。例如,英国的遗产:

  我曾经穿着睡衣拍了一头大象。他怎么进我的pajams我永远不会知道   
- 格劳乔·马克思

“穿着我的睡衣”这句话暧昧地描述了主题“我”或物体“大象”。没有语义上下文,就无法正确构建AST。

如果你想避免这种情况,你可能需要一些以有用的方式处理歧义的方法。一种策略是产生模糊短语的所有可能的推导。使这成为可能的一个工具是Earley Parser。与其他类型的解析器(如递归下降解析器)不同,Earley解析器以解析器状态转换的形式生成所有派生,而不是简单的树。在实践中,这不是更难以使用。

答案 6 :(得分:0)

使用Flex和Bison: Bison中的语法规则:

%%

English            :   SentenceList

SentenceList       :   Sentence
                   |   Article  Sentence
                   |   Sentence Conjunction Sentence

Sentence           :   Noun Verb


Conjunction        :   TOKEN_WordAnd
                   |   TOKEN_WordOr
                   |   TOKEN_WordBut


Noun               :   TOKEN_WORD_BIRDS
                   |   TOKEN_WORD_FISH
                   |   TOKEN_WORD_CPP

Verb:              :   TOKEN_WORD_RULES
                   |   TOKEN_WORD_FLY
                   |   TOKEN_WRD_SWIM

Article            :   TOKEN_WORD_THE
%%

答案 7 :(得分:0)

你可以把战利品带到Ubiquity,它是Firefox的一个插件,旨在使用自然语言来执行常见的Web任务(它是用JavaScript编写的,但也许你可以得到一个有用的通用算法)< / p>

答案 8 :(得分:-1)

在你写一个解析器之前,我可能建议学习一对lex和yacc或flex和bison吗?这些工具专门用于创建解析器和词法分析器。

他们将自动生成您的c / c ++(可能是其他)代码,因此您不必担心用户参数的各种边界情况。你可以在30分钟内完成上面的语法。

关于你的问题:

  

对于单词functions(noun,verb,   等我应该怎么去检查   如果他们是真的? (如检查是否   用户的输入有鸟,鱼,苍蝇,   游泳等。)

这里要求大量使用strcasecmp(),并进行各种错误检查。

  

我应该如何处理连接   打电话和输出?

我真的不太了解你在这里的意思。如果有效或无效,我只会返回某种哨兵价值。

  

我应该处理来自的输出   主要功能还是通话功能?

主要来自通话功能,因为它们具有您所关注的个性化功能。 main()只是粘合在一起。

  

以上问题都不重要   我的伪代码是完全错误的。是   基础知识有什么问题吗?

看起来你的方式可行,但是你可以通过切换到lex / yacc或flex / bison来节省大量的麻烦