使用attoparsec以递归方式返回.txt文件中的所有单词

时间:2018-05-04 04:57:16

标签: parsing haskell attoparsec

我是Haskell的新手,我刚刚开始学习如何使用attoparsec从.txt文件中解析大块的英文文本。我知道如何在不使用attoparsec的情况下获取.txt文件中的单词数量,但我有点坚持使用attoparsec。当我在下面运行我的代码时,让我们说

  

“Hello World,我是Elliot Anderson。\ n我是Mr.Robot。\ n”

我才回来:

  

世界,我是艾略特安德森。 \ n我是Mr.Robot。\ n“(散文{word =   “你好”})

这是我目前的代码:

{-# LANGUAGE OverloadedStrings #-}
import Control.Exception (catch, SomeException)
import System.Environment (getArgs)
import Data.Attoparsec.Text
import qualified Data.Text.IO as Txt
import Data.Char
import Control.Applicative ((<*>), (*>), (<$>), (<|>), pure)

{-
This is how I would usually get the length of the list of words in a .txt file normally.

countWords :: String -> Int
countWords input = sum $ map (length.words) (lines input)

-}

data Prose = Prose {
  word :: String
} deriving Show

prose :: Parser Prose
prose = do
  word <- many' $ letter
  return $ Prose word

main :: IO()
main = do
  input <- Txt.readFile "small.txt"
  print $ parse prose input

另外,我怎样才能获得单词的整数计数?关于如何开始使用attoparsec的任何建议?

2 个答案:

答案 0 :(得分:3)

你已经有一个很好的开始 - 你可以解析一个词 您接下来需要的是Parser [Prose],可以通过使用prosesepBysepBy1解析器与使用“非散文”部分的另一个解析器相结合来表达,您可以在Data.Attoparsec.Text文档中查找。

从那里,获得单词计数的最简单方法是简单地获得所获得的[Prose]的长度。

编辑:

这是一个最小的工作示例。已将Parser转轮换成parseOnly以允许忽略剩余输入,这意味着尾随的非单词不会使解析器变为cray-cray。

{-# LANGUAGE OverloadedStrings #-}

module Atto where

--import qualified Data.Text.IO as Txt
import Data.Attoparsec.Text
import Control.Applicative ((*>), (<$>), (<|>), pure)

import qualified Data.Text as T

data Prose = Prose {
  word :: String
} deriving Show

optional :: Parser a -> Parser ()
optional p = option () (try p *> pure ())

-- Modified to disallow empty words, switched to applicative style
prose :: Parser Prose
prose = Prose <$> many1' letter

separator :: Parser ()
separator = many1 (space <|> satisfy (inClass ",.'")) >> pure ()

wordParser :: String -> [Prose]
wordParser str = case parseOnly wp (T.pack str) of
    Left err -> error err
    Right x -> x
    where
        wp = optional separator *> prose `sepBy1` separator

main :: IO ()
main = do
  let input = "Hello World, I am Elliot Anderson. \nAnd I'm Mr.Robot.\n"
  let words = wordParser input
  print words
  print $ length words

提供的解析器与concatMap words . lines的结果不完全相同,因为它还会破坏.,'上的单词。修改此行为只是一个简单的练习。

希望它有所帮助! :)

答案 1 :(得分:2)

你走在正确的轨道上!您编写了一个解析器(prose),它只读取一个单词:many' letter识别一系列字母。

现在您已经想出如何解析单个单词,您的工作就是扩展它以解析由空格分隔的单词序列。这就是sepBy的作用:p `sepBy` q重复运行p解析器并散布q解析器。

因此,一系列单词的解析器看起来像这样(我冒昧地将prose重命名为word):

word = many letter
phrase = word `sepBy` some space  -- "some" runs a parser one-or-more times

ghci> parseOnly phrase "wibble wobble wubble"  -- with -XOverloadedStrings
Right ["wibble","wobble","wubble"]

现在,由phraseletter组成的space将会死在非字母非空格字符上,例如'. 。我会留给你弄清楚如何解决这个问题。 (作为提示,您可能需要将many letter更改为many (letter <|> ...),具体取决于您希望它在各种标点符号上的行为方式。)