Alex Rivera | Logout

How do I parse a matrix of integers in Haskell?

Asked 2011-12-03T06:42:16.020
11

So I've read the theory, now trying to parse a file in Haskell - but am not getting anywhere. This is just so weird...

Here is how my input file looks:

        m n
        k1, k2...

        a11, ...., an
        a21,....   a22
        ...
        am1...     amn

Where m,n are just intergers, K = [k1, k2...] is a list of integers, and a11..amn is a "matrix" (a list of lists): A=[[a11,...a1n], ... [am1... amn]]

Here is my quick python version:

def parse(filename):
    """
    Input of the form:
        m n
        k1, k2...

        a11, ...., an
        a21,....   a22
        ...
        am1...     amn

    """

    f = open(filename)
    (m,n) = f.readline().split()
    m = int(m)
    n = int(n)

    K = [int(k) for k in f.readline().split()]

    # Matrix - list of lists
    A = []
    for i in range(m):
        row = [float(el) for el in f.readline().split()]
        A.append(row)

    return (m, n, K, A)

And here is how (not very) far I got in Haskell:

import System.Environment
import Data.List

main = do
    (fname:_) <- getArgs
    putStrLn fname --since putStrLn goes to IO ()monad we can't just apply it
    parsed <- parse fname
    putStrLn parsed

parse fname = do
    contents <- readFile fname
    -- ,,,missing stuff... ??? how can I get first "element" and match on it?

    return contents

I am getting confused by monads (and the context that the trap me into!), and the do statement. I really want to write something like this, but I know it's wrong:

firstLine <- contents.head
(m,n) <- map read (words firstLine)

because contents is not a list - but a monad.

Any help on the next step would be great.

So I've just haskell

Edit
Report

2 Answers

8

A Solution Using a Parsing Library

Since you'll probably have a number of people responding with code that parses strings of Ints into [[Int]] (map (map read . words) . lines $ contents), I'll skip that and introduce one of the parsing libraries. If you were to do this task for real work you'd probably use such a library that parses ByteString (instead of String, which means your IO reads everything into a linked list of individual characters).

import System.Environment
import Control.Monad
import Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString as B

First, I imported the Attoparsec and bytestring libraries. You can see these libraries and their documentation on hackage and install them using the cabal tool.

main = do
    (fname:_) <- getArgs
    putStrLn fname
    parsed <- parseX fname
    print parsed

main is basically unchanged.

parseX :: FilePath -> IO (Int, Int, [Int], [[Int]])
parseX fname = do
    bs <- B.readFile fname
    let res = parseOnly parseDrozzy bs
    -- We spew the error messages right here
    either (error . show) return res

parseX (renamed from parse to avoid name collision) uses the bytestring library's readfile, which reads in the file packed, in contiguous bytes, instead of into cells of a linked list. After parsing I use a little shorthand to return the result if the parser returned Right result or print an error if the parser returned a value of Left someErrorMessage.

-- Helper functions, more basic than you might think, but lets ignore it    
sint = skipSpace >> int
int = liftM floor number

parseDrozzy :: Parser (Int, Int, [Int], [[Int]])
parse
answered 2011-12-03T09:00:58.640
5

What about something simple like this?

parse :: String -> (Int, Int, [Int], [[Int]])
parse stuff = (m, n, ks, xss)
        where (line1:line2:rest) = lines stuff
              readMany = map read . words
              (m:n:_) = readMany line1
              ks = readMany line2
              xss = take m $ map (take n . readMany) rest

main :: IO ()
main = do
        stuff <- getContents
        let (m, n, ks, xss) = parse stuff
        print m
        print n
        print ks
        print xss 
answered 2011-12-03T15:04:31.130

Your Answer