5

学习使用 Parsec 库,这是家庭作业的一部分。

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

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

我想不通的是如何忽略(吃掉)任何不是指南针方向的输入。我正在寻找类似的东西

'many (not compassDirection >> space)'

但是 g(h)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
4

3 回答 3

4

我将告诉你我将如何开始,然后建议我将如何继续。

我将其基于抽象数据结构 - 当您添加额外的单词时,您可以更仔细地对它们进行分类:

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

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

applicative 风格比 monadic 风格更好。

我认为compassDirection应该允许大写:

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

您可以定义country使用Country <$> ((:) <$> upper <*> many lower)

然后你可以有一个包罗万象的Unclassified <$> many letter

您的单词解析器当前可以是

word = compassDirection <|> country <|> unclassified

但请注意compassDirection必须在前面country,否则country会匹配North

你可以做

words = word `sepBy1` space

目前没关系,但你一定不能使用wordwords当你更正确地分析句子时,因为你无法控制单词是什么。那时您需要noun, adjective, nounPhrase, verb, adjective,adjectivalPhrase等来传递句子结构。您无法解析的句子意味着您需要在语法中添加新的结构。

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

使用 applicative 和更高阶的函数将使你的语法更清晰,因为你不会把它与一元符号混在一起,它看起来像句子。示例:nvn = NVN <$> noun <*> verb <*> noun如果您想使用主要是抽象数据结构 (AST) 方法,每个语法对象有一个构造函数,您可以这样做。如果你只是喜欢有一堆相同类型的单词,你可以这样做nvn = sequence [noun,verb,noun]

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

如果您坐下来写下如何将单词、短语、从句和句子的类别组合在一起,您将能够相当快地编写解析器。

于 2012-10-26T18:20:29.733 回答
4

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


我解决这个问题的方法:

  • 标记输入。把它分解成文字;这将使真正的解析步骤不必担心令牌定义(即“是 %#@[ 是单词的一部分吗?”)或空格。这可以很简单,words或者您可以使用 Parsec 进行标记化。然后你就会拥有[Token](或者[String]如果你愿意)。

  • 指南针方向的解析器。你已经有了这个(干得好),但如果输入[String]不是String.

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

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

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

  • 一个适用于大量令牌的解析器

希望这很清楚,但不太清楚;例如,您仍然需要担心何时丢弃垃圾。基本思想是将问题分解为许多小子问题,解决子问题,然后将这些解决方案粘合在一起。

于 2012-10-26T16:11:47.733 回答
0

您不能将字符串拆分为以大写字母开头或指南针方向的字符串wordsfilter然后将unwords它们重新组合在一起吗?不需要Parsec拔枪。

于 2012-10-26T17:24:43.680 回答