I think you've overcomplicated the problem because you're approaching it the way you would in an imperative language. I'll describe the way I would approach the problem. Notice that I do a lot of my "thinking" in GHCi!
Your code reads the file and splits it up into lines just fine, so I'll move onto the part where we handle each line of text. First, we need to split the line up into tokens, where the tokens are separated by commas. The words
function is close to what we want, but it breaks at whitespace, not commas. So we'll write our own:
tokens :: String -> [String]
tokens [] = []
tokens as =
case break (==',') as of
(xs,[]) -> [xs]
(xs,_:ys) -> xs:tokens ys
I put that in the program I'm writing, load it, and try it out:
λ> tokens "wombat,7,789,tiger,33"
["wombat","7","789","tiger","33"]
Now we need to separate the words from the integers. So we need a function that will tell us if a token is an integer. The problem statement implies we only need to deal with integers. So a token is an integer if it only contains digits. (The example given didn't have any spaces, but we could allow them, and even decimal points, if we want to.)
So how do we tell if a character is a digit? Hmm... there's probably a function to do that for us.The best way to answer questions of the form "Is there a Haskell function to do X" is usually:
Figure out what the type signature of the function you want would be.
Search hoogle or hayoo. If you don't find it in one, try the other. Usually it doesn't matter much whether you get the order of the input parameters exactly right, but you may need to experiment.
What would the type of the function we're interested in be? Logically, it has to be:
Char -> Bool
Unfortunately, in the case there are a lot of functions with that signature, but eventually we find isDigit
in Data.Char
.
λ> import Data.Char
λ> isDigit '7'
True
λ> isDigit 'w'
False
Look at the source for that function. (You'll see that it would be easy to write our own function to allow blanks or decimal points as well as digits.)
So now we have a way to tell if an individual character is valid in an integer. How do we tell if all of the characters in a token pass the test? Again, I suspect there's a function that applies a boolean test to all elements in a sequence, and tells us if they all pass. The type signature of such a function would be one of the following:
(a -> Bool) -> [a] -> Bool
[a] -> (a -> Bool) -> Bool
Checking Hoogle, we find the all
function in Data.List
.
λ> import Data.List
λ> all isDigit "123"
True
λ> all isDigit "wombat"
False
We could use this expression as is, but would be more readable to define a function.
isInteger :: String -> Bool
isInteger s = all isDigit s
We could also write that last line using pointfree notation.
isInteger = all isDigit
I add that to my program, reload it, and try it out:
λ> isInteger "123"
True
λ> isInteger "wombat"
False
Next we need to divide our tokens up into those that are integers, and those that aren't. Again, there's probably a function that segregates a list into those items that satisfy a test, and those that don't. It would have this signature (possibly with the arguments switched):
(a -> Bool) -> [a] -> ([a],[a])
This time a Hayoo or Hoogle search turns up break
and partition
. Looking at the documentation, we see that partition
is what we want. Let's try it:
λ> partition isInteger ["wombat","7","789","tiger","33"]
(["7","789","33"],["wombat","tiger"])
That preserved the order of the elements, but it gave us the integers first. That's easily fixed:
λ> partition (not . isInteger) ["wombat","7","789","tiger","33"]
(["wombat","tiger"],["7","789","33"])
Let's wrap that in a function to make it more readable:
segregate :: [String] -> ([String], [String])
segregate = partition (not . isInteger)
I'm going to move a little more quickly now, because you're probably getting the hang of how to approach these problems. Next we need to format the results. Let's focus on re-inserting the comma between tokens. I'll write a dual to the tokens
function:
untokens :: [String] -> String
untokens = concat . intersperse ","
Let's try it:
λ> untokens ["wombat","tiger"]
"wombat,tiger"
Now all we need to combine the two parts:
format :: ([String],[String]) -> String
format (xs,ys) = untokens xs ++ '|' : untokens ys
Let's try it out:
λ> format (["wombat","tiger"],["7","789","33"])
"wombat,tiger|7,789,33"
Now we can put all the steps together:
parse :: String -> String
parse = format . segregate . tokens
See how simple and readable the parse
function is? And it works:
λ> parse "wombat,7,789,tiger,33"
"wombat,tiger|7,789,33"
EDIT: Here's the entire program, with main
.
import Data.Char
import Data.List
main = do
args <- getArgs
content <- readFile(args !! 0)
mapM_ (putStrLn . lines) content
tokens :: String -> [String]
tokens [] = []
tokens as =
case break (==',') as of
(xs,[]) -> [xs]
(xs,_:ys) -> xs:tokens ys
untokens :: [String] -> String
untokens = concat . intersperse ","
segregate :: [String] -> ([String], [String])
segregate = partition (not . isInteger)
isInteger :: String -> Bool
isInteger = all isDigit
format :: ([String],[String]) -> String
format (xs,ys) = untokens xs ++ '|' : untokens ys
parse :: String -> String
parse = format . segregate . tokens