Haskell IO多行读取技术

时间:2015-01-08 18:56:20

标签: haskell io

基本上我想找到一种方法,以便用户可以输入测试用例的数量,然后输入他们的测试用例。然后,程序可以运行这些测试用例,并按测试用例出现的顺序打印出结果。

所以基本上我有main读取测试用例的数量并将其输入到将从IO读取多次的函数中。它看起来像这样:

main = getLine >>= \tst -> w (read :: String -> Int) tst [[]]

这是w:w :: Int -> [[Int]]-> IO ()

的方法签名

所以我的计划是读取测试用例的数量并运行一个函数,该函数接收每个测试用例并将结果存储到[[]]变量中。因此列表中的每个列表都是输出。 w将以递归方式运行,直到达到0并在单独的行上打印出每个列表。我想知道是否有更好的方法可以做到这一点,因为我必须将一个空列表传入w,这似乎是无关紧要的。

2 个答案:

答案 0 :(得分:3)

正如@bheklilr所提到的,你无法更新像[[]]这样的值。标准函数方法是通过一组递归调用传递累加器。在以下示例中,acc函数的loop参数是此累加器 - 它包含到目前为止收集的所有输出。在循环结束时我们返回它。

myTest :: Int -> [String]
myTest n = [ "output line " ++ show k ++ " for n = " ++ show n | k <- [1..n] ]

main = do
  putStr "Enter number of test cases: "
  ntests <- fmap read getLine :: IO Int
  let loop k acc | k > ntests = return $ reverse acc
      loop k acc = do
        -- we're on the kth-iteration
        putStr $ "Enter parameter for test case " ++ show k ++ ": "
        a <- fmap read getLine :: IO Int
        let output = myTest a         -- run the test
        loop (k+1) (output:acc)
  allOutput <- loop 1 []
  print allOutput

当你对这种模式更加熟悉时,你会认为它是一个 fold (实际上是我们正在进行IO时的monadic折叠)你可以用{{1}来实现它}。

更新:为了帮助解释fmap的工作原理,这里是不使用foldM编写的等效表达式:

fmap

使用With fmap: Without fmap: n <- fmap read getLine :: IO [Int] line <- getLine let n = read line :: Int vals <- fmap (map read . words) getLine line <- getLine :: IO [Int] let vals = (map read . words) line :: [Int] 允许我们消除我们从未再次引用的中间变量fmap。我们仍然需要提供类型签名,以便line知道该怎么做。

答案 1 :(得分:2)

惯用法是使用replicateM

runAllTests :: [[Int]] -> IO ()
runAllTests = {- ... -}

main = do
    numTests <- readLn
    tests <- replicateM numTests readLn
    runAllTests tests
-- or:
-- main = readLn >>= flip replicateM readLn >>= runAllTests