Haskell中是否有getInt函数?

4
我希望在Haskell上拥有一个“getInt :: IO Int”函数。这个函数将从标准输入中获取一个整数,并保留缓冲区中的其余内容。
我已经找到了这种类型的库函数,“readLn :: IO Int”。但是,例如,对于像这样的输入,它将无法工作:
2
3 4

因为它会占用整个行 3 4,而不是只拿走 3 并将 4 留给下一个 getInt。虽然我知道可以读取完整的字符串,然后使用 words 进行拆分,但我想问是否有一种不消耗缓冲区的方法。
标准库中是否有任何函数可以完成此操作?是否有任何简单/明显的方法可以创建一个我错过的 getInt

1
你的函数是否可以接受 Int 之间的分隔符?如果可以,那么编写一个循环调用 getChar 直到看到分隔符,然后对生成的 String 调用 readIO 看起来很简单。 - Daniel Wagner
也许在getLine的实现中将\n更改为 或类似的内容会起作用(?)。http://hackage.haskell.org/package/base-4.9.0.0/docs/src/GHC.IO.Handle.Text.html#hGetLine - Mario Román
基本上就是我所描述的,不过一开始我建议跳过标准 getLine 实现中所有缓冲区感知的花哨操作。 - Daniel Wagner
哦,太好了,我没有看到那个评论。我认为它会完美地工作。谢谢,Daniel Wagner。 - Mario Román
4个回答

4
您可以使用 hLookAhead 函数并捕获 isEOFError 异常来编写此类函数。然而,这会将您的IO与解析混合在一起。

更具Haskell风格的解决方案是定义:

parseInts :: String -> [Int]
parseInts str = map read (words str)

然后像这样使用parseInt

-- read an entire file and convert to a list of Ints
nums <- fmap parseInts getContents


-- read just a line and convert to a list of Ints
nums <- fmap parseInts getLine

解析类似于以下内容的文件:

3 4
1 1 1 1
2 2 2 2 
3 3 3 3

(例如,3 = 行数,4 = 列数),你有很多选项:
import Control.Monad (replicateM)

main = do
  (nrows : ncols : _) <- fmap parseInts getLine
  rows <- replicateM nrows $ fmap parseInts getLine

或者

import Data.List.Split (chunksOf)

main = do
  (nrows : ncols : rest) <- fmap parseInts getContents
  let rows = chunksOf ncols rest

或者说甚至:

main = do
  (nrows : ncols : _) <- fmap parseInts getLine
  forM_ [1..nrows] $ \i -> do
    row <- fmap parseInts getLine
    ... do something with row...

3

在这里,我将我的评论转化为答案,因为原始提问者似乎可以接受使用分隔符。首先,我们将定义一个getWord类似于getLine;然后我们将readIO结果。我们会小心地捕获EOF异常以防发生异常。

import Control.Exception
import Control.Applicative
import Data.Char
import System.IO.Error

getWord :: IO String
getWord = handle handleEOF $ do
  c <- getChar
  if isSpace c then return [] else (c:) <$> getWord
  where
  handleEOF e = if isEOFError e then return [] else throwIO e

readWord :: Read a => IO a
readWord = getWord >>= readIO

请注意:对于要读取的具有空格(如Data.Map或其他复杂数据类型)的值,这种方法显然不会很好地工作。


1
如果遇到EOF会发生什么?为了做到这一点的稳健性,我认为您需要使用hLookAhead - ErikR
@ErikR 我已经更新了 getWord 函数,使其能够优雅地处理 EOF。 - Daniel Wagner
为什么不使用 handleJust :: Exception e => (e -> Maybe b) -> (b -> IO a) -> IO a -> IO ahandleJust (guard . isEOFError) (\() -> return []) $ do ... - dfeuer
@dfeuer 为什么你的版本更好?当前代码简单易读,表意明确。你的版本有哪些优势? - Daniel Wagner
@DanielWagner,这只是避免显式重新抛出异常。您也可以使用handle来避免翻转。 - dfeuer

2

有没有一种简单/明显的方法来创建一个我错过的getInt?

没有。你正在寻找类似于scanf(“%d”)的东西,它忽略所有前导空格,并且如果没有数字,则不更改缓冲区。在这种情况下,您需要向前查看:

import Data.Char (isSpace, isDigit, digitToInt)
import Data.List (foldl')
import Control.Monad (when)
import System.IO (hGetChar, hLookAhead, Handle, stdin)

getInt :: IO (Maybe Int)
getInt = hGetInt stdin

hGetIntegral :: (Integral n) => Handle -> IO (Maybe n)
hGetIntegral h = do 
  hSkipSpace h
  digits <- hGetDigits h
  return $ case digits of
    [] -> Nothing
    xs -> Just $ foldl' (\x a -> 10 * a + x) 0 . map digitToInt $ xs

hGetDigits :: Handle -> IO [Char]
hGetDigits h = do
  la <- hLookAhead h
  if isDigit la then hGetChar h >> fmap (la:) (hGetDigits h)
                else return []

hSkipSpace :: Handle -> IO ()
hSkipSpace h = do
  la <- hLookAhead h
  when (isSpace la) $ hGetChar h >> hSkipSpace h

顺便提一下,withCString "%d" $ \str -> with 0 $ \i -> c_scanf str i >> peek i 的行为与 scanf("%d", %i) 相同。但这只是为了好玩而已,不应与其他 Haskell 函数混用。 - Zeta
很好地使用了 hLookAhead。也许我们还应该检查一下 isEOF - chi

0
readInt :: [Char] -> Maybe Int
readInt [] = Nothing
readInt str = case reads str :: [(Int, String)] of
    [(x ,"")] -> (Just x)
    _ -> Nothing

getInt :: IO (Maybe Int)
getInt = do
    nums <- getLine
    return (readInt nums)

给你,如果需要的话可以去掉"maybe",通过修改来适当放置它

祝好运


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接