Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to input several values in one line in haskell

Tags:

haskell

For example, I want to write a program which will take 3 integers as input from command line. The functions I have learned is readLn to read values from entire line. But readLn seems to parse the entire line as a single value. How can I get the three values of one line with haskell?

like image 776
richard.g Avatar asked Feb 19 '14 15:02

richard.g


2 Answers

There several ways you could go about this, but I'd prefer the one using reads. This function has type Read a => ReadS a, where ReadS a is just a type alias for String -> [(a, String)]. It pulls characters from the provided string, stopping when it no longer successfully parses, then returns a list containing a tuple of the parsed value and the remaining string. It uses a list for a more flexible definition, but in practice you'll never see it return a list more than 1 element. An example would be

> reads "1 2 3" :: [(Int, String)]
[(1, " 2 3")]
> reads " 2 3" :: [(Int, String)]
[(2, " 3")]

Using this function, we can parse a line for Ints and it'll automatically stop when it fails to parse one or the string is empty. We just need a way to chain together multiple calls. Ideally, we'd like a function with the type (s -> [(a, s)]) -> s -> [a] so that we can re-use it:

chain :: (s -> [(a, s)]) -> s -> [a]
chain f s = case f s of
    [] -> []
    [(a, newS)] -> a : chain f newS
    xs -> map fst xs ++ chain f (last $ map snd xs)

Then you can use it as

> chain reads "1 2 3" :: [Int]
[1, 2, 3]
> chain reads "1 2 3 asdf" :: [Int]
[1, 2, 3]
> chain reads "asdf 1 2 3" :: [Int]
[]

Then you just have to do

read3Ints :: String -> [Int]
read3Ints = take 3 . chain reads
like image 41
bheklilr Avatar answered Sep 27 '22 21:09

bheklilr


Read a line with getLine, split it into words, and read each:

readInts :: IO [Int]
readInts = fmap (map read.words) getLine

it reads any number of Ints:

ghci> readInts
1 2 3
[1,2,3]
ghci> readInts
1 2 3 4 5 6
[1,2,3,4,5,6]

You could restrict to three:

read3Ints :: IO [Int]
read3Ints = do
     ints <- readInts
     if length ints == 3 then return ints else do
         putStrLn ("sorry, could you give me exactly three integers, "
                  ++ "separated by spaces?")
         read3Ints

which looks like this:

ghci> read3Ints
1 2
sorry, could you give me exactly three integers, separated by spaces?
1 23 , 5, 6
sorry, could you give me exactly three integers, separated by spaces?
1,2,3
sorry, could you give me exactly three integers, separated by spaces?
1 3 6

The secrets of fmap

fmap works a bit like map, but you can use it more widely:

ghci> fmap (*10) [1,2,3,4]
[10,20,30,40]
ghci> fmap (*10) (Just 5)
Just 50
ghci> map (fmap (*10)) [Left 'b', Right 4, Left 'c', Right 7]
[Left 'b',Right 40,Left 'c',Right 70]
ghci> fmap words getLine
Hello there me hearties!
["Hello","there","me","hearties!"]

In getInts, I did fmap (map read.words) to split the line by spaces, then map read to turn each one into an Int. The compiler knows I wanted Int because of the type signature - I'd get an error if I omitted it.

like image 122
AndrewC Avatar answered Sep 27 '22 21:09

AndrewC