user1501127
user1501127

Reputation: 865

Have a list of strings but need a list of Integers

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

Answers (4)

Gabriella Gonzalez
Gabriella Gonzalez

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 Chars to Ints:

charToInt :: Char -> Int
charToInt = stringToInt . charToString

Now, we can lift that function to process an entire list of Chars 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

KAction
KAction

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

AndrewC
AndrewC

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

user180247
user180247

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

Related Questions