Is getLine
lazy?
Say I have a very long line on the input. It's just a sequence of numbers. I only need to sum 3 first numbers. Will getLine
be efficient and read only the first part of the line, or do I have to create my own function for lazy line reading, that would read characters one by one?
Will my implementation be efficient if I were to sum the whole line? (Will there be an overhead due to reading characters one by one?)
import Control.Applicative
main = do
line <- getLine'
print $ sum $ map read $ take 3 $ words line
getLine' :: IO String
getLine' = do
c <- getChar
if c == '\n' then return [] else (c:) <$> getLine'
While getLine
isn't lazy, getContents
is, and it can be combined with functions like lines
and words
. Therefore, the following program will only read enough of stdin to get (up to) three integers from the first line and print their sum:
main :: IO ()
main = do contents <- getContents
let lns = lines contents
result = sum $ map read $ take 3 $ words $ head lns
print (result :: Integer)
Note that, if you modify the program to access subsequent lines -- for example, if you added:
putStrLn $ take 80 $ lns !! 1
to the bottom of the program to print the first 80 characters of the second line, then the program would have to finish reading the first line (and so would hang for a bit between the last two lines of the program) before processing the first 80 characters of the second. In other words, this lazy line reading is only useful if you only need to read the first bit of the first line, if that wasn't obvious to you -- Haskell doesn't have any magic way to skip the rest of the first line to get to the second.
Finally, note that, for the above program, if there are fewer than three integers on the first line, it'll just sum those numbers and won't try to read past the first line (which I think is what you wanted). If you don't actually care about the line endings and just want to sum the first three numbers in the file, regardless of how they're divided up into lines, then you can break the contents up directly into words like so:
main = do contents <- getContents
let result = sum $ map read $ take 3 $ words contents
print (result :: Integer)