“尝试”回溯到多远?

时间:2018-07-23 16:26:51

标签: haskell parsec megaparsec

所以...我弄乱了CSV格式的录音:

23,95489,0,20,9888

由于语言设置,浮点数用逗号作为分隔符写在逗号分隔的值文件中...

问题是该文件的每个浮动格式都没有很好的格式。有些根本没有点,并且点后面的数字也有所不同。

我的想法是建立一个MegaParsec解析器,该解析器将尝试读取每种可能的浮点格式,继续前进,如果发现错误,则返回原位。

例如上述示例:

  1. 阅读23,95489->好
  2. 读0,20->好(到目前为止)
  3. 读取9888->错误(因为列的值太高(由guard检查))
  4. (回溯到2。)再次读取0->好
  5. 阅读20,9888->好
  6. 完成

我已经将其实现为(此处为伪代码):

floatP = try pointyFloatP <|> unpointyFloatP

lineP = (,,) <$> floatP <* comma <*> floatP <* comma <*> floatP <* comma

我的问题是,显然try仅在“当前”浮点数中起作用。没有回溯到以前的职位。这是正确的吗?

如果是的话...我将如何实施进一步的回溯追踪?

2 个答案:

答案 0 :(得分:3)

  

“尝试”回溯到多远?

如果try p成功解析,则解析器p会消耗与p一样多的输入,否则它根本不会消耗任何输入。因此,如果从回溯的角度来看,它会回溯到调用它时的位置。

  

我的问题是,显然,该尝试仅在“当前”浮点数中有效。没有回溯到以前的职位。这是正确的吗?

是的,try不会“消耗”输入。它所做的一切就是从您提供给它的解析器中的故障中恢复而无需消耗任何输入。它不会撤消您先前应用的任何解析器的影响,也不会影响您在try p成功之后应用的后续解析器。

  

如果是的话...我将如何实施进一步的回溯追踪?

基本上,您想要的不仅是知道pointyFloatP在当前输入上是否成功,而且还要知道lineP的其余部分在pointyFloatP成功之后是否会成功-以及是否成功您是否想回溯到应用pointyFloatP之前的状态。因此,基本上,您需要try中整个行的解析器,而不仅仅是浮动解析器。

要实现这一点,您可以使floatP将其余行的解析器作为这样的参数:

floatP restP = try (pointyFloatP <*> restP) <|> unpointyFloatP <*> restP

请注意,这种回溯将不会非常有效(但我想您已经知道了)。

答案 1 :(得分:2)

更新:包括用于更复杂行的自定义monadic解析器。

使用列表Monad进行简单解析

与Megaparsec相比,列表monad可以更好地回溯“解析器”。例如,解析单元格:

row :: [String]
row = ["23", "95489", "0", "20", "9888"]

准确地将三列值满足特定范围(例如,小于30),可以使用以下方法生成所有可能的解析:

{-# OPTIONS_GHC -Wall #-}

import Control.Monad
import Control.Applicative

rowResults :: [String] -> [[Double]]
rowResults = cols 3
  where cols :: Int -> [String] -> [[Double]]

        cols 0 [] = pure []   -- good, finished on time
        cols 0 _  = empty     -- bad, didn't use all the data

        -- otherwise, parse exactly @n@ columns from cells @xs@
        cols n xs = do
          -- form @d@ from one or two cells
          (d, ys) <- num1 xs <|> num2 xs
          -- only accept @d < 30@
          guard $ d < 30
          ds <- cols (n-1) ys
          return $ d : ds

        -- read number from a single cell
        num1 (x:xs) | ok1 x = pure (read x, xs)
        num1 _ = empty

        -- read number from two cells
        num2 (x:y:zs) | ok1 x && ok2 y = pure (read (x ++ "." ++ y), zs)
        num2 _ = empty

        -- first cell: "0" is okay, but otherwise can't start with "0"
        ok1 "0" = True
        ok1 (c:_) | c /= '0' = True
        ok1 _ = False

        -- second cell: can't end with "0" (or *be* "0")
        ok2 xs = last xs /= '0'

上述基于列表的解析器通过假设如果“ xxx,yyy”为数字,则“ xxx”将不会以零开头(除非只是“ 0”),而“ yyy”会尝试减少歧义不会以零结尾(或者就此而言,为单个“ 0”)。如果不正确,只需适当地修改ok1ok2

应用于row,这将提供单一的解析:

> rowResults row
[[23.95489,0.0,20.9888]]

应用于不明确的行,它给出所有解析:

> rowResults ["0", "12", "5", "0", "8601"]
[[0.0,12.5,0.8601],[0.0,12.5,0.8601],[0.12,5.0,0.8601]]

无论如何,我建议使用标准CSV解析器将文件解析为String个单元格的矩阵,如下所示:

dat :: [[String]]
dat = [ ["23", "95489", "0", "20", "9888"]
      , ["0", "12", "5", "0", "8601"]
      , ["23", "2611", "2", "233", "14", "422"]
      ]

然后使用上方的rowResults获取模棱两可的行的行数:

> map fst . filter ((>1) . snd) . zip [1..] . map (length . rowResults) $ dat
[2]
>

或无法分析:

> map fst . filter ((==0) . snd) . zip [1..] . map (length . rowResults) $ dat
[]
>

假设没有不可解析的行,即使某些行模棱两可,您也可以重新生成一个可能的固定文件,而只是获取每行的第一个成功解析:

> putStr $ unlines . map (intercalate "," . map show . head . rowResults) $ dat
23.95489,0.0,20.9888
0.0,12.5,0.8601
23.2611,2.233,14.422
>

使用基于列表Monad的自定义Monad进行更复杂的解析

对于更复杂的解析,例如,如果您想解析像这样的行:

type Stream = [String]
row0 :: Stream
row0 = ["Apple", "15", "1", "5016", "2", "5", "3", "1801", "11/13/2018", "X101"]

混合使用字符串和数字,实际上,基于列表monad编写可生成所有可能解析的monadic解析器并不难。

关键思想是将一个解析器定义为一个函数,该函数接受流并生成可能解析的列表,每个可能解析都表示为从对象开始成功解析的对象的 tuple 。流与流的其余部分配对。用新类型包装后,我们的并行解析器将如下所示:

newtype PParser a = PParser (Stream -> [(a, Stream)]) deriving (Functor)

请注意与ReadS中的类型Text.ParserCombinators.ReadP相似,从技术上讲,它也是“所有可能的解析器”解析器(尽管您通常只希望从reads返回一个明确的解析器致电):

type ReadS a = String -> [(a, String)]

无论如何,我们可以像这样为Monad定义一个PParser实例:

instance Applicative PParser where
  pure x = PParser (\s -> [(x, s)])
  (<*>) = ap
instance Monad PParser where
  PParser p >>= f = PParser $ \s1 -> do  -- in list monad
    (x, s2) <- p s1
    let PParser q = f x
    (y, s3) <- q s2
    return (y, s3)

这里没有什么棘手的问题:pure x返回一个可能的解析,即结果x具有不变的流s,而p >>= f应用第一个解析器{{ 1}}生成可能的解析列表,将它们逐一地放在列表monad中,以计算下一个要使用的解析器p(通常,对于monadic操作,解析器可能取决于解析结果)第一次解析),并生成返回的可能最终解析的列表。

qAlternative实例非常简单-它们只是从列表monad中消除了空虚和交替:

MonadPlus

要运行解析器,我们需要:

instance Alternative PParser where
  empty = PParser (const empty)
  PParser p <|> PParser q = PParser $ \s -> p s <|> q s
instance MonadPlus PParser where

现在我们可以介绍基元:

parse :: PParser a -> Stream -> [a]
parse (PParser p) s = map fst (p s)

和组合器:

-- read a token as-is
token :: PParser String
token = PParser $ \s -> case s of
  (x:xs) -> pure (x, xs)
  _ -> empty

-- require an end of stream
eof :: PParser ()
eof = PParser $ \s -> case s of
  [] -> pure ((), s)
  _ -> empty

并在CSV行中解析各种“术语”:

-- combinator to convert a String to any readable type
convert :: (Read a) => PParser String -> PParser a
convert (PParser p) = PParser $ \s1 -> do
  (x, s2) <- p s1     -- for each possible String
  (y, "") <- reads x  -- get each possible full read
                      -- (normally only one)
  return (y, s2)

然后,对于特定的行模式,我们可以像通常使用monadic解析器那样编写行解析器:

-- read a string from a single cell
str :: PParser String
str = token

-- read an integer (any size) from a single cell
int :: PParser Int
int = convert (mfilter ok1 token)

-- read a double from one or two cells
dbl :: PParser Double
dbl = dbl1 <|> dbl2
  where dbl1 = convert (mfilter ok1 token)
        dbl2 = convert $ do
          t1 <- mfilter ok1 token
          t2 <- mfilter ok2 token
          return $ t1 ++ "." ++ t2

-- read a double that's < 30
dbl30 :: PParser Double
dbl30 = do
  x <- dbl
  guard $ x < 30
  return x

-- rules for first cell of numbers:
-- "0" is okay, but otherwise can't start with "0"
ok1 :: String -> Bool
ok1 "0" = True
ok1 (c:_) | c /= '0' = True
ok1 _ = False

-- rules for second cell of numbers:
-- can't be "0" or end in "0"
ok2 :: String -> Bool
ok2 xs = last xs /= '0'

并获得所有可能的解析:

-- a row
data Row = Row String Int Double Double Double
               Int String String deriving (Show)
rowResults :: PParser Row
rowResults = Row <$> str <*> int <*> dbl30 <*> dbl30 <*> dbl30
                 <*> int <*> str <*> str <* eof

完整程序为:

> parse rowResults row0
[Row "Apple" 15 1.5016 2.0 5.3 1801 "11/13/2018" "X101"
,Row "Apple" 15 1.5016 2.5 3.0 1801 "11/13/2018" "X101"]
>

现成的解决方案

我感到有点奇怪,因为我找不到在那里提供这种“所有可能的解析器”解析器的现有解析器库。 {-# LANGUAGE DeriveFunctor #-} {-# OPTIONS_GHC -Wall #-} import Control.Monad import Control.Applicative type Stream = [String] newtype PParser a = PParser (Stream -> [(a, Stream)]) deriving (Functor) instance Applicative PParser where pure x = PParser (\s -> [(x, s)]) (<*>) = ap instance Monad PParser where PParser p >>= f = PParser $ \s1 -> do -- in list monad (x, s2) <- p s1 let PParser q = f x (y, s3) <- q s2 return (y, s3) instance Alternative PParser where empty = PParser (const empty) PParser p <|> PParser q = PParser $ \s -> p s <|> q s instance MonadPlus PParser where parse :: PParser a -> Stream -> [a] parse (PParser p) s = map fst (p s) -- read a token as-is token :: PParser String token = PParser $ \s -> case s of (x:xs) -> pure (x, xs) _ -> empty -- require an end of stream eof :: PParser () eof = PParser $ \s -> case s of [] -> pure ((), s) _ -> empty -- combinator to convert a String to any readable type convert :: (Read a) => PParser String -> PParser a convert (PParser p) = PParser $ \s1 -> do (x, s2) <- p s1 -- for each possible String (y, "") <- reads x -- get each possible full read -- (normally only one) return (y, s2) -- read a string from a single cell str :: PParser String str = token -- read an integer (any size) from a single cell int :: PParser Int int = convert (mfilter ok1 token) -- read a double from one or two cells dbl :: PParser Double dbl = dbl1 <|> dbl2 where dbl1 = convert (mfilter ok1 token) dbl2 = convert $ do t1 <- mfilter ok1 token t2 <- mfilter ok2 token return $ t1 ++ "." ++ t2 -- read a double that's < 30 dbl30 :: PParser Double dbl30 = do x <- dbl guard $ x < 30 return x -- rules for first cell of numbers: -- "0" is okay, but otherwise can't start with "0" ok1 :: String -> Bool ok1 "0" = True ok1 (c:_) | c /= '0' = True ok1 _ = False -- rules for second cell of numbers: -- can't be "0" or end in "0" ok2 :: String -> Bool ok2 xs = last xs /= '0' -- a row data Row = Row String Int Double Double Double Int String String deriving (Show) rowResults :: PParser Row rowResults = Row <$> str <*> int <*> dbl30 <*> dbl30 <*> dbl30 <*> int <*> str <*> str <* eof row0 :: Stream row0 = ["Apple", "15", "1", "5016", "2", "5", "3", "1801", "11/13/2018", "X101"] main = print $ parse rowResults row0 中的内容采用了正确的方法,但是它假设您是在解析Text.ParserCombinators.ReadP中的字符,而不是其他流中的任意标记(在我们的示例中是{a}中的String s) String

也许其他人可以指出一种现成的解决方案,它可以使您不必扮演自己的解析器类型,实例和基元。