2012-10-26 31 views
5

学习使用Parsec库,作业的一部分。Haskell Parsec跳过没有预定义的所有单词

编辑:欢迎使用其他库的建议,重点是解析。

我想要的是从任何句子中提取大写字母和四个指南针方向的所有单词。例如:“比利时完全位于荷兰南部。”应该找到并返回“比利时南荷兰”。

我无法理解的是如何忽略(吃)任何不是指南针方向的输入。 我希望找到沿

'many (not compassDirection >> space)' 

但克(1H)oogle是没有帮助我行的东西。

下面的代码明显停留在“多”功能上。

readExpr :: String -> String 
readExpr input = case parse (parseLine) "" input of 
    Left err -> "No match: " ++ show err 
    Right val -> "Found: " ++ showVal val 

parseLine :: Parser GraphValue 
parseLine = do 
      x <- parseCountry 
      space 
      many (some (noneOf " ") >> space) 
      y <- parseCompass 
      space 
      many (some (noneOf " ") >> space) 
      z <- parseCountry 
      return $ Direction [x,y,z] 

compassDirection :: Parser String 
compassDirection = string "north" <|> 
        string "south" <|> 
        string "east" <|> 
        string "west" 

parseCountry :: Parser GraphValue 
parseCountry = do 
       c <- upper 
       x <- many (lower) 
       return $ Country (c:x) 

parseCompass :: Parser GraphValue 
parseCompass = do 
       x <- compassDirection 
       return $ Compass x 
+1

(只是风格上,你可以写'compassDirection =选择$地图字符串[“北”,“南”,“东”,“西”]'。) – huon

+0

做得好诚实,清晰,解决目前为止的问题并提供您现有的代码。一个很好的问题。 +1 – AndrewC

回答

4

我不会详谈,因为这是家庭作业,OP说“重要的是解析”。


我会解决这个问题的方法:

  • 令牌化的输入。分解成单词;这将释放真正的解析步骤而不必担心令牌定义(即“是%#@ [一部分的词?”)还是空白。这可能与words一样简单,或者您可以使用Parsec进行标记。那么你将有[Token](或[String]如果你愿意)。

  • 指南针方向的解析器。你已经有了这个(很好的工作),但是如果输入是[String]而不是String,它将不得不修改一下。

  • 对于以大写字母开头的单词的解析器。

  • 其他所有解析器,只要它看到一个不是指南针方向的标记或一个以大写字母开头的单词就会成功。

  • 解析器适用于任何标记,但区分好的东西和不好的东西,可能使用代数数据类型。

  • 上很多记号的工作

希望这显然没有被解析器太清晰;例如,您仍然需要担心何时丢弃垃圾。其基本思想是将问题分解成许多小问题,解决子问题,然后将这些解决方案粘合在一起。

0

难道你不能把字符串拆分成words,filter那些以大写字母开头或是指南针方向的字符串,然后unwords他们回来吗?无需拔出Parsec枪。

+1

这只是我得到的基础知识的窍门。最终,我们应该尽可能地分析自然语言。 “德国和意大利不共享边界​​,但比利时和瑞典都这样做。” 我想我最好的选择是开始弄清楚如何解析真正的基本预定义句子。 – Taelia

3

我打算告诉你我将如何开始,然后就如何继续进行建议。

我的依据是一个抽象的数据结构 - 当您添加多余的话,你可以更紧密地将其分类:

data Word = Country String | Direction NSEW | Unclassified String 
data NESW = North | East | South | West 

,所以你不知道我的回答对你如何跳过的话大约是您不需要 - 将它们保留为未分类。

应用风格比monadic风格更好。

我觉得compassDirection应该让资本:

compassDirection :: Parser NESW 
compassDirection = north <|> south <|> east <|> west where 
    north = North <$ (string "north" <|> string "North") 
    east = ... 

可以使用Country <$> ((:) <$> upper <*> many lower)

然后你就可以拥有一个包罗万象的Unclassified <$> many letter定义country

你的话解析器目前可以

word = compassDirection <|> country <|> unclassified 

但是请注意,compassDirection有来country之前,否则country将匹配North

你可以在这行的时刻做

words = word `sepBy1` space 

,但是你必须要一定不正确使用wordwords当你分析句子,因为你失去了这个词,是什么样的控制。在这一点上,你需要noun,adjective,nounPhrase,verb,adjective,adjectivalPhrase等等来诠释句子结构。你无法解析的句子意味着你需要在语法中添加新的构造。

值得让词语解析器吞下它们后面的空白(或之前),或者使用预处理器重构空格和标点符号中的单词。如果您是英国人,请考虑使用fullStop解析器,如果您是美国人,请考虑使用解析器period。在创建句子分析器时使用它。

使用应用程序和更高阶的函数将使你的语法写得更清晰,因为你不会把它与一元符号混杂在一起,它会看起来像句子。 示例:如果要针对每个语法对象使用主要具有抽象数据结构(AST)方法和一个构造函数,则可以执行nvn = NVN <$> noun <*> verb <*> noun。 如果你只是喜欢围绕所有相同类型放置一堆单词,你可以做nvn = sequence [noun,verb,noun]

大多数计算机语言都是使用AST方法解析的,但是除了我从我妻子的语言学学位获得的二手资料之外,我没有直接的自然语言解析经验。

如果您坐下来写下如何将单词,短语,从句和句子的类别组合在一起,您就可以快速编写解析器。

+0

令人惊叹的答案,谢谢你的努力。我会让信息沉浸在我身上,然后我会尝试实现所有这些。 – Taelia