2015-08-15 12 views
3

我正在嘗試使解析器掃描文本文件中由空行分隔的數字數組。解析虛框之間的數字數組

1 235 623 684 
2 871 699 557 
3 918 686 49 
4 53 564 906 


1 154 
2 321 
3 519 

1 235 623 684 
2 871 699 557 
3 918 686 49 

以下是完整的text file

我寫了parsec以下解析器:

import Text.ParserCombinators.Parsec 

emptyLine = do 
    spaces 
    newline 

emptyLines = many1 emptyLine 

data1 = do 
    dat <- many1 digit 
    return (dat) 

datan = do 
    many1 (oneOf " \t") 
    dat <- many1 digit 
    return (dat) 

dataline = do 
    dat1 <- data1 
    dat2 <- many datan 
    many (oneOf " \t") 
    newline 
    return (dat1:dat2) 

parseSeries = do 
    dat <- many1 dataline 
    return dat 

parseParag = try parseSeries 

parseListing = do 
    --cont <- parseSeries `sepBy` emptyLines 
    cont <- between emptyLines emptyLines parseSeries 
    eof 
    return cont 

main = do 
    fichier <- readFile ("test_listtst.txt") 
    case parse parseListing "(test)" fichier of 
      Left error -> do putStrLn "!!! Error !!!" 
          print error 
      Right serie -> do 
           mapM_ print serie 

但它失敗,出現以下錯誤:

!!! Error !!! 
"(test)" (line 6, column 1): 
unexpected "1" 
expecting space or new-line 

和我不不明白爲什麼。

你知道我的解析器有什麼問題嗎?

你有沒有關於如何解析由空行分隔的結構化數據的例子?

+3

請添加您的導入... – Jubobs

+0

我添加了導入文件 – JeanJouX

+0

我的意思是:在您的Haskell源文件中添加'import ...'行。因爲你的代碼片段不是獨立的。 – Jubobs

回答

1

你知道我的解析器有什麼問題嗎?

幾件事:

  1. 作爲其它回答者已經指出的那樣,spaces解析器被設計爲消耗滿足Data.Char.isSpace字符序列;換行符('\n')就是這樣一個字符。因此,您的emptyLine解析器總是失敗,因爲newline需要一個已被使用的換行符。

  2. 無論如何,你可能不應該在你的「行」解析器中使用newline解析器,因爲如果後者不以換行符結束,這些解析器將在文件的最後一行失敗。

  3. 爲什麼不使用parsec 3(Text.Parsec.*)而不是parsec 2(Text.ParserCombinators.*)?

  4. 爲什麼不把數字解析爲Integer s或Int s,而不是將它們保留爲String s?

  5. 個人喜好,但你太過依賴我的口味do表示法,不利於可讀性。例如,

    data1 = do 
        dat <- many1 digit 
        return (dat) 
    

    可以簡化爲

    data1 = many1 digit 
    
  6. 你會做好的類型簽名添加到您的所有頂級綁定。

  7. 要如何命名您的分析器是一致的:爲什麼「parseListing」而不是簡單的「列表」?

  8. 您是否考慮過使用不同類型的輸入流(例如Text)以獲得更好的性能?

你對如何解析由空行分隔數據的結構化一堆的例子嗎?

下面是你想要的一種解析器的簡化版本。注意,輸入不應該以空行開始(但可以以空行結束),並且「數據行」不應當包含前導空間,但可以包含尾隨空格(在spaces解析器的意義上) 。

module Main where 

import Data.Char (isSpace) 
import Text.Parsec 
import Text.Parsec.String (Parser) 

eolChar :: Char 
eolChar = '\n' 

eol :: Parser Char 
eol = char eolChar 

whitespace :: Parser String 
whitespace = many $ satisfy $ \c -> isSpace c && c /= eolChar 

emptyLine :: Parser String 
emptyLine = whitespace 

emptyLines :: Parser [String] 
emptyLines = sepEndBy1 emptyLine eol 

cell :: Parser Integer 
cell = read <$> many1 digit 

dataLine :: Parser [Integer] 
dataLine = sepEndBy1 cell whitespace 
--   ^
-- replace by endBy1 if no trailing whitespace is allowed in a "data line" 

dataLines :: Parser [[Integer]] 
dataLines = sepEndBy1 dataLine eol 

listing :: Parser [[[Integer]]] 
listing = sepEndBy dataLines emptyLines 

main :: IO() 
main = do 
    fichier <- readFile ("test_listtst.txt") 
    case parse listing "(test)" fichier of 
     Left error -> putStrLn "!!! Error !!!" 
     Right serie -> mapM_ print serie 

測試:

λ> main 
[[1,235,623,684],[2,871,699,557],[3,918,686,49],[4,53,564,906]] 
[[1,154],[2,321],[3,519]] 
[[1,235,623,684],[2,871,699,557],[3,918,686,49]] 
0

我不知道確切的問題,但我用parsec解析「面向行」的文件的經驗是:不要使用parsec(或至少不是這種方式)。

我指的問題是你想以某種方式剝奪號碼之間的空格(空格和換行)(在同一行),但仍需要時意識到它們。 這一步真的很難做到(這就是你要做的)。 這可能是可以添加的前瞻,但它很雜亂(說實話,我從來沒有設法使其工作)。

最簡單的方法是分析的第一步行(其允許您檢測空行),然後分別分析每一行。

爲此,您根本不需要parsec,只需使用lineswords即可。但是,這樣做,你就失去了回溯的能力。

有可能是一個方法來「多張步」用秒差距分析,它的標記生成器(但我沒有找到有關如何使用秒差距標記生成任何有用的文檔)。

+0

* [...]請勿使用parsec [...] *我恭敬地不同意。這裏所需的解析器與[真實世界Haskell *的第16章](http://book.realworldhaskell.org/read/using-parsec.html)中的解析器沒有多大區別,parsec處理得很好。 – Jubobs

+0

有一個小小的差異,這是所有的行在RHW例子中類似,這使得它很容易使用parsec進行分析。這裏的問題是解析空行但不跳過它們。我在過去添加類似的問題,並且最終開始使用parsec。但是,我可能是錯的,也許OP問題比我的簡單,很容易解決。或者,也許我的問題比我容易。我很想看到OP問題的解決方案。我可能會把它翻譯成地雷。 – mb14

+0

我認爲使用parsec的真正問題在於它必須讀取整個文件才能使分析成功 - 即您無法在識別它們時處理元素。 – ErikR

2

spacesemptyLine消耗'\n',然後newline沒有'\n'解析。你可以把它寫成:

emptyLine = do 
    skipMany $ satisfy (\c -> isSpace c && c /= '\n') 
    newline 

而且你應該改變parseListing到:

parseListing = do 
    cont <- parseSeries `sepEndBy` emptyLines 
    eof 
    return cont 

我覺得sepEndBysepBy更好,因爲它會忽略你可能在年底的任何新線文件。

2

幾件事情:

spaces包括新的生產線,等等spaces >> newline總是失敗,這意味着該emptyLine解析器總是會失敗。

我有運氣的parseSeriesparseListing這些定義:

parseSeries = do 
    s <- many1 dataline 
    spaces     -- eat trailing whitespace 
    return s 

parseListing = do 
    spaces     -- ignore leading whitespace 
    ss <- many parseSeries -- begin parseSeries at non-whitespace 
    eof 
    return ss 

的想法是,一個解析器總是吃空白它後面。 這種方法也處理空文件。

1

這裏是另一種方法,它允許你在數據流,因爲它是識別處理每個塊:

import Data.Char 
import Control.Monad 

-- toBlocks - convert a list of lines into a list of blocks 
toBlocks :: [String] -> [[[String]]] 
toBlocks [] = [] 
toBlocks theLines = 
    let (block,rest) = break isBlank theLines 
     next = dropWhile isBlank rest 
    in if null block 
     then toBlocks next 
     else [ words x | x <- block ] : toBlocks next 
    where isBlank str = all isSpace str 

main' path = do 
    content <- readFile path 
    forM_ (toBlocks (lines content)) $ print 

秒差距已經在讀整個文件之前,它給你的塊列表,如果你的輸入文件很大,這可能是一個問題。