【问题标题】:Parser in Haskell for TriplesHaskell 中的三元组解析器
【发布时间】:2017-07-05 00:54:04
【问题描述】:

我目前正在做有关 Haskell 解析的作业,但我在一些基础知识方面遇到了困难。

作业: 我应该创建一个将字符串解析为三元组列表的函数。 以便: 甲、乙、丙 ,E,D

会导致

Triples [("A","B","C"), ("A","E","D")]

输入字符串将包含 ;\n 作为新三元组开始的指示。字符串将以点结尾。 三元组的元素可以是字母或数字或组合, 例如abc, a, 1, abc121.

因此,

"a,b,c;\n d,e;\n f,g;\n h,i."  

会导致:

Triples [("a","b","c"),("a","d","e"),("a","f","g"),("a","h","i")]

我目前的解决方案:

parseTriplesD :: Parser Triples
parseTriplesD = parseTriples 
            >>= \rs -> return (Triples rs)

这个函数非常简单和正确。获取字符串并返回一个新类型 Triples 的对象,其中包含由 parseTriples 创建的 Triples 列表。

parseTriples :: Parser [Triple]
parseTriples = parseTriple 
            >>= \r -> ((string ";\n" >> parseTriples >>= \rs -> return (r:rs)) 
            P.<|>(return[r]))

这个功能需要一些工作。我的想法是我使用另一个函数创建一个带有输入字符串的树元素的 Triple,忽略 /n 并递归调用它,同时将创建的三元组添加到返回列表中。当这不起作用时,因为它只能创建一个 Triple,它会返回一个包含 Triple 的列表。 我不知何故需要创建第一个三元组,然后使用这个三元组的第一个元素作为其他三元组的第一个元素。

问题 1 如何创建第一个三元组并将三元组的第一个元素用于其他三元组?

parseTriple :: Parser Triple
parseTriple = P.many (letter<|>digit) >>= \a -> P.char ','
            >> P.many (letter<|>digit)>>= \b -> P.char ','
            >> P.many (letter<|>digit)>>= \c -> return ((a,b,c))

这个函数很简单,但我不确定它是否正确。 我的想法是,它需要字符串的前几个字符(字母或数字),直到逗号 "," ,并将这些字符保存在 a 中。 重复 3 次,然后创建并返回一个包含三个元素的 Triple。

问题 2 如何只取字符串的几个字符(可以是字母或数字 EDIT: 或空格字符)直到逗号? P.many(字母数字)是否正确?

我们得到了什么:

Triples 数据结构:

newtype Triples = Triples [Triple] deriving (Show,Eq)
type Triple = (String, String, String) 

进口:

import Test.HUnit (runTestTT,Test(TestLabel,TestList),(~?=))
import qualified Text.Parsec as P (char,runP,noneOf,many,(<|>),eof)
import Text.ParserCombinators.Parsec
import Text.Parsec.String 
import Text.Parsec.Char
import Data.Maybe

测试用例

runParsec :: Parser a -> String -> Maybe a
runParsec parser input = case P.runP parser () "" input of
    Left  _ -> Nothing
    Right a -> Just a

-- | Tests the implementations of 'parseScore'.
main :: IO ()
main = do
    testresults <- runTestTT tests
    print testresults

-- | List of tests for 'parseScore'.
tests :: Test
tests = TestLabel "parseScoreTest" (TestList [
    runParsec parseTriplesD "0,1,2;\n2,3."   ~?= Just (Triples [("0","1","2"),("0","2","3")]),
    runParsec parseTriplesD "a,bcde ,23." ~?= Just (Triples [("a","bcde ","23")]),
    runParsec parseTriplesD "a,b,c;\n d,e;\n f,g;\n h,i." ~?= Just (Triples [("a","b","c"),("a","d","e"),("a","f","g"),("a","h","i")]),
    runParsec parseTriplesD "a,bcde23." ~?= Nothing,
    runParsec parseTriplesD "a,b,c;d,e;f,g;h,i." ~?= Nothing,
    runParsec parseTriplesD "a,b,c;\nd;\nf,g;\nh,i." ~?= Nothing
    ])

【问题讨论】:

  • 您的第二个测试显示在"bcde " 的预期输出中捕获的空间。我以为你只想要输出中的字母数字字符。是错字吗?
  • 啊。错过了那个。我们没有得到直接的任务指示,只有测试用例。所以我认为它只是字母数字字符,但我完全错过了那个空格字符。我将在我的问题中对其进行编辑。

标签: parsing haskell functional-programming


【解决方案1】:

你可以做的是:

  1. 解析第一个字符
  2. 解析对列表
  3. 将第一个字符添加到每对以创建三元组

使用do 表示法将使您的代码更具可读性。
您可以使用alphaNum 作为letter &lt;|&gt; digit 的简写。

parseTriplesD :: Parser Triples
parseTriplesD = Triples <$> parseTriples

parseTriples :: Parser [Triple]
parseTriples = do
    a <- parseString
    char ','
    pairs <- parsePair `sepBy1` string ";\n"
    char '.'
    eof
    return (map (\(b, c) -> (a, b, c)) pairs)

parsePair :: Parser (String, String)
parsePair = do
    first <- parseString
    char ','
    second <- parseString
    return (first, second)

parseString :: Parser String
parseString = many (char ' ') >> many (alphaNum <|> char ' ')

【讨论】:

  • 好主意。没想到。你能解释一下 pair sepBy1 (string ";\n" >> optional (char ' ')) 吗? “sepBy 1” 和 optional 有什么作用?
  • 这是sepBy1optional 的文档。 sepBy1 将解析由第二个参数分隔的第一个参数列表。 optional 将尝试解析某些内容,但如果匹配失败则忽略它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多