Reputation: 865
I have a list with a string inside it and I need to have that deconstructed Char by Char and put into a list as Integer instead but I'm stymied by the types
What i have is a txt file that i read into monad:
getTxt = do
y <- readFile "foo.txt"
return y
foo
only contains this:
"1234567890\n"
then I thought I was close with sequence but that gets me this list:
["1","2","3","4","5","6","7","8","9","0"] :: [[Char]]
but I need [Integer]
. ord
will take Char -> Int
but how do I read that [Char] -> [Int]
? And after all these trial and only error, don't I need to filter out that last new line in the end?
Any suggestions?
Upvotes: 1
Views: 366
Reputation: 35089
So you want a function that has this type:
charsToInts :: [Char] -> [Int]
We can solve this by decomposing the problem into smaller problems. First, we need a function that converts a single Char
to a String
:
charToString :: Char -> String
charToString c = [c]
... then we need a function that converts a String
to an Int
:
stringToInt :: String -> Int
stringToInt = read
... then we compose those two functions to get a function that converts Char
s to Int
s:
charToInt :: Char -> Int
charToInt = stringToInt . charToString
Now, we can lift that function to process an entire list of Char
s by using map
:
charsToInts :: [Char] -> [Int]
charsToInts = map charToInt
... and we're done!
I took a very verbose path just for demonstrative purposes. In my own code I would typically inline all these definitions like so:
charsToInts :: [Char] -> [Int]
charsToInts = map (read . singleton)
where singleton x = [x]
To use stringsToInts
in your code, you would just write:
getTxt :: IO [Int]
getTxt = fmap charsToInts $ readFile "foo.txt"
That fmap
applies charsToInts
to the result of readFile
, and the above code is equivalent to:
getTxt = do
chars <- readFile "foo.txt"
return $ charsToInts chars
[outside comment:
You can reduce it even further, with a list comprehension:
getTxt :: IO [Int]
getTxt = do
chars <- readFile "foo.txt"
return [read [d] | d <- chars]
Notice that while type annotations for top-level functions are generally a good idea, in this case it is mandatory (unless you put an annotation into the function body). That is because "read" otherwise doesn't know what type you want. ]
Upvotes: 0
Reputation: 2017
Read documentation of map
and filter
. It it very important.
In your case
integersFromFile :: String -> IO [Int]
integersFromFile filename = map digitToInt <$> readFile filename
Upvotes: 0
Reputation: 32455
If you use ord
, the types match, but it's not what you want because ord
gives you
the ascii value, not the numeric value: ord 5
is 53
, not 5
. You could subtract
48 to get the digit, then roll the digits up into a single number, but it would be
easier to use a library function. The most straightforward choice is read
:
getInt :: IO Integer
getInt = do
y <- readFile "foo.txt"
return (read (takeWhile (/='\n') y))
As in the linked answer,
the best solution here is to use reads
.
reads
finds a list of possible matches,
as pairs of (match,remainingstring)
,
which works well for you because it will automatically leave the newline in the remaining string,
*Main> reads "31324542\n" :: [(Integer,String)]
[(31324542,"\n")]
Let's use that:
findInt :: String -> Maybe Integer
findInt xs = case reads xs of -- have a look at reads xs
((anint,rest):anyothers) -> Just anint -- if there's an int at the front of the list, just return it
_ -> Nothing -- otherwise return nothing
Maybe
's a handy data type that lets you have failure without crashing the program or doing exception handling.
Just 5
means you got output and it's 5
. Nothing
means there was a problem, no output.
addTen :: FilePath -> IO ()
addTen filename = do
y <- readFile filename
case findInt y of
Just i -> putStrLn ("Added 10, got "++show (i+10))
Nothing -> putStrLn ("Didn't find any integer at the beginning of " ++ filename)
Which gives you:
*Main> addTen "foo.txt"
Added 10, got 1234567890
If you just want the integers the characters represent, you can put import Data.Char
at the top of your file and do
ordzero = ord '0' -- handy constant, 48, to unshift the ascii code to a digit.
getInts :: FilePath -> IO [Int] -- ord gives the smaller Int, not Integer
getInts filename = do
y <- readFile filename
return [ord achar - ordzero | achar <- takeWhile isDigit y]
This takes the characters of the string y
for as long as they're digits,
then finds their ord
, subtracting ord '0'
(which is 48) to turn '4'
into 4
etc.
Upvotes: 1
Reputation:
I'm not sure I understand what you're saying, but my version of what I think illusionoflife is suggesting is a list comprehension...
do cs <- readFile "foo.txt"
return [ord c | c <- cs, c /= '\n']
This is a bit of a cheat - it assumes the file will only contain digits and that end-of-line, and just strips out any end-of-line characters wherever they occur.
Explanation - this is a list comprehension. The c <- cs
basically assigns c
each character in turn. The c /= '\n'
filters out cases with the line-end (wherever it occurs - it doesn't have to be at the end). The ord c
gives the values to include in the final list.
This could be expressed using filter
and map
, but once you get used to it, a list comprehension is much more convenient.
An improved version might use isDigit
(from Data.Char) to check characters. There also Maybe a way to track whether there are invalid characters in the list, so you can either check for and report those markers later or filter them out.
Upvotes: 0