patternMinor
Parsing 2D matrix: "[[1,2][3,4]]"
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
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
Second, if it is an exersise, you can avoid long chains of
Third, you can avoid passing unparsed input around by keeping it in the
If you want to stay with your current approach then
Here is full parser using
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
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.