HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Parsing 2D matrix: "[[1,2][3,4]]"

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
matrixparsingstackoverflow

Problem

I feel I am over-complicating things, and that there should be a succinct way to achieve this - perhaps using concepts I have not learnt - or some prelude function I have missed out on.

How can I improve this?

```
data Mat2x2 = MkMat2x2 Integer Integer Integer Integer
deriving (Show, Eq)

-- modified versions of preludes readParanthesis
readBrackets :: ReadS a -> ReadS a
readBrackets g = mandatory
where mandatory r = do
("[", s) Maybe (Integer, String)
readInt s = listToMaybe ((reads :: String -> [(Integer, String)]) s)

-- greedy consumption of comma separated values and returns them as list and remaining string, ex:
-- parseCsv readInt "2,3,5,2"
-- ([2,3,5,2],"")
parseCsv :: (String -> Maybe (a, String)) -> String -> ([a], String)
parseCsv f s = case (f s) of
Nothing -> ([], s)
Just (x,(',':r)) -> (x : (nextXs r), (nextR r))
Just (x,r) -> ([x], r)
where nextXs ns = fst (parseCsv f ns)
nextR ns = snd (parseCsv f ns)

-- reads an arbitrary length vector "[1,2,3,...N]" -> Just [1,2,3...,N]
readVec :: String -> Maybe ([Integer], String)
readVec s = listToMaybe (readBrackets ((:[]) . parseCsv readInt) s) -- note: (:[]) is operator making a list of one element

readVec2 :: String -> Maybe ((Integer, Integer), String)
readVec2 s = case readVec s of
Just ((a:[b]), r) -> Just ((a,b), r)
Just (_, _) -> Nothing
Nothing -> Nothing

read2Vec2 :: String -> Maybe (((Integer, Integer),(Integer,Integer)), String)
read2Vec2 s = case readVec2 s of
Nothing -> Nothing
Just ((a,b), r1) -> case readVec2 r1 of
Nothing -> Nothing
Just ((c,d), r2) -> Just (((a,b),(c,d)), r2)

readMat2x2 :: Stri

Solution

Many improvements are possible.

First, you shouldn't use ReadS and other rudumentary Prelude parsing function for anything but exercises. Use parsing libraries from Hackage instead. parsec is a good library to start with, if you are not sure of which one to use.

Second, if it is an exersise, you can avoid long chains of Nothing matches by using Maybe monad. I reimplemented read2Vec2 for example:

read2Vec2 :: String -> Maybe (((Integer, Integer),(Integer,Integer)), String)
read2Vec2 s = do
    ((a,b), r1) <- readVec2 s
    ((c,d), r2) <- readVec2 r1
    Just (((a,b),(c,d)), r2)


Third, you can avoid passing unparsed input around by keeping it in the State monad. At this point our code will do what parsec does :) except parsec does it better.

If you want to stay with your current approach then readBrackets can be shortened:

readBrackets :: ReadS a -> ReadS a
readBrackets g r = do
    ("[", s) <- lex r
    (x,t)    <- g s
    ("]", u) <- lex t
    return (x,u)


Here is full parser using parsec:

{-# LANGUAGE NoMonomorphismRestriction #-}
import Text.ParserCombinators.Parsec
import Control.Applicative

data Mat2x2 = MkMat2x2 Integer Integer Integer Integer
  deriving (Show, Eq)

parseMat2x2 = parse mat2x2 "(unknown)" 

nat = read  many1 digit 

mat2x2 = do
    string "[["
    d1 <- nat
    char ','
    d2 <- nat
    string "]["
    d3 <- nat
    char ','
    d4 <- nat
    string "]]"
    return $ MkMat2x2 d1 d2 d3 d4

main = print $ parseMat2x2 "[[2,2][3,4]]"


The parser can be made less strict if necessary. For example, current parser doesn't accept any whitespace. And for a better performance you could try parsec with a lexer stage (current code avoids lexer altogether).

For ultimate performance you should use alex and happy tools, which are haskell's lex and yacc respectively.

Code Snippets

read2Vec2 :: String -> Maybe (((Integer, Integer),(Integer,Integer)), String)
read2Vec2 s = do
    ((a,b), r1) <- readVec2 s
    ((c,d), r2) <- readVec2 r1
    Just (((a,b),(c,d)), r2)
readBrackets :: ReadS a -> ReadS a
readBrackets g r = do
    ("[", s) <- lex r
    (x,t)    <- g s
    ("]", u) <- lex t
    return (x,u)
{-# LANGUAGE NoMonomorphismRestriction #-}
import Text.ParserCombinators.Parsec
import Control.Applicative

data Mat2x2 = MkMat2x2 Integer Integer Integer Integer
  deriving (Show, Eq)

parseMat2x2 = parse mat2x2 "(unknown)" 

nat = read <$> many1 digit 

mat2x2 = do
    string "[["
    d1 <- nat
    char ','
    d2 <- nat
    string "]["
    d3 <- nat
    char ','
    d4 <- nat
    string "]]"
    return $ MkMat2x2 d1 d2 d3 d4

main = print $ parseMat2x2 "[[2,2][3,4]]"

Context

StackExchange Code Review Q#71241, answer score: 3

Revisions (0)

No revisions yet.