有以下任务:
示例:
Input:
2
5
1 2 3 4 5
2
-100 100
Output:
15
0
这是我的实施
import Control.Monad
main = do
linesCount <- readLn :: IO Int
numbers <- replicateM linesCount getCase
mapM_ putStrLn $ map (show.sum) numbers
getCase :: IO [Int]
getCase = do
numbersCount <- readLn :: IO Int -- actually I don't need this variable
numbersString <- getLine
let numbers = map read $ words numbersString
return numbers
它看起来像解析输入的代码很多。是否有任何技巧来压缩&#34;它? :)
答案 0 :(得分:3)
如果您只想缩短代码,请查看Stack Exchange community for code golfing。这主要是为了娱乐和游戏。
如果我们认为代码太多,可能不是我们需要缩短代码,而是我们需要让它更清晰。实现这一目标取决于经验和良好实践。我们想要做的是分离明显正确的简单概念,然后以明显正确的方式将它们组合起来。方法包括自上而下的设计(将解决方案分解成更小的部分)和自下而上的设计(从较小的部分到解决方案)及其混合。
直接击中我的自下而上的作品是总结一系列数字的任务。这在Haskell的Prelude中有一个名为sum :: (Num a, Foldable t) => t a -> a
的定义。在最终解决方案的某个地方,我们将使用它。
另一种方法是简化问题。问题的表达方式我们可能会误入歧途。经过仔细检查,我们可能会发现一个等同且更简单的措辞。
我们从输入中实际需要哪些信息?只是数字列表。获取数字列表的最简单方法是什么?列表的数量似乎无关紧要,因为在我们开始查看列表之前不需要获取此信息。放下第一行,我们留下:
5
1 2 3 4 5
2
-100 100
然后,每个列表的长度也无关紧要,因为在汇总列表之前我们不需要这些信息。因此,我们也可以从这一点删除所有其他行:
1 2 3 4 5
-100 100
现在我们只有按行返回的数字列表,其中每个数字用空格分隔。
此时我们有一种明确的方式以自上而下的方式分解解决方案。首先,我们简化输入。其次,我们解析数字列表。第三,我们总结了这些清单。第四,我们打印总和。因此,这是我们解决方案的骨架:
simplifyInput :: String -> [String]
parseNumberList :: String -> [Integer]
-- Note we can use `sum` from Prelude to sum the number lists.
printSums :: [Integer] -> IO ()
main :: IO ()
main = getContents >>= printSums . fmap (sum . parseNumberList) . simplifyInput
现在只需要实现解决方案的每一个显而易见的部分。
simplifyInput :: String -> [String]
simplifyInput = dropEveryOther . drop 1 . lines
where
dropEveryOther :: [a] -> [a]
在编写simplifyInput
时,我发现丢弃其他每一行都需要更多的工作。没关系,我们可以再次将解决方案分开。
dropEveryOther :: [a] -> [a]
dropEveryOther [] = []
dropEveryOther (x:y:xs) = y : dropEveryOther xs
然后继续......
parseNumberList :: String -> [Integer]
parseNumberList = fmap read . words
printSums :: [Integer] -> IO ()
printSums = putStr . unlines . fmap show
因此,总体而言:
simplifyInput :: String -> [String]
simplifyInput = dropEveryOther . drop 1 . lines
where
dropEveryOther :: [a] -> [a]
dropEveryOther [] = []
dropEveryOther (_:y:xs) = y : dropEveryOther xs
parseNumberList :: String -> [Integer]
parseNumberList = fmap read . words
printSums :: [Integer] -> IO ()
printSums = putStr . unlines . fmap show
main :: IO ()
main = getContents >>= printSums . fmap (sum . parseNumberList) . simplifyInput
我们的代码量已经增加(与第一个解决方案相比),但作为交换,代码变得明显。现在您应该添加一些文档注释,这样我们就不会忘记解决方案的解释。
答案 1 :(得分:2)
Alec在其中一条评论中发布了原始代码的超级压缩版本。我决定发布一个小故障,万一有人迷路了,不知道那里发生了什么:)
下面的代码片段必须以有效的导入开头:
import Control.Monad
import Control.Applicative
所以我们从Alec的版本开始:
main = readLn >>= flip replicateM_ (getLine >> sum . map read . words <$> getLine >>= print)
他使用flip
函数删除一组括号:
main = readLn >>= (`replicateM_` (getLine >> (print =<< sum . map read . words <$> getLine)))
他使用replicateM_
的中缀表示法来部分应用replicateM_
的第二个参数,我们可以替换为lambda:
main = readLn >>= \n -> replicateM_ n (getLine >> (print =<< sum . map read . words <$> getLine))
现在让我们开始将一些代码提取到单独的有意义的函数中:
printBatchResult = print =<< sum . map read . words <$> getLine
main = readLn >>= \n -> replicateM_ n (getLine >> printBatchResult)
我们可以翻转print =<<
以获得更多可读性:
printBatchResult = sum . map read . words <$> getLine >>= print
main = readLn >>= \n -> replicateM_ n (getLine >> printBatchResult)
等等:
printBatchResult = sum . map read . words <$> getLine >>= print
handleBatch = getLine >> printBatchResult
main = readLn >>= \n -> replicateM_ n handleBatch
再次:
sumLine = sum . map read . words
printBatchResult = sumLine <$> getLine >>= print
handleBatch = getLine >> printBatchResult
main = readLn >>= \n -> replicateM_ n handleBatch
还有一次:
sumLine = sum . map read . words
handleNumbersLine = sumLine <$> getLine
printBatchResult = handleNumbersLine >>= print
handleBatch = getLine >> printBatchResult
main = readLn >>= (\n -> replicateM_ n handleBatch)
最后一次:
sumLine = sum . map read . words
handleNumbersLine = sumLine <$> getLine
printBatchResult = handleNumbersLine >>= print
handleBatch = getLine >> printBatchResult
handleAllBatches n = replicateM_ n handleBatch
main = readLn >>= handleAllBatches
我们可以将<$>
替换为fmap
:
sumLine = sum . map read . words
handleNumbersLine = fmap sumLine getLine
printBatchResult = handleNumbersLine >>= print
handleBatch = getLine >> printBatchResult
handleAllBatches n = replicateM_ n handleBatch
main = readLn >>= handleAllBatches
我们还可以删除所有部分应用程序:
sumLine line = (sum . map read . words) line
handleNumbersLine = fmap sumLine getLine
printBatchResult = handleNumbersLine >>= \sum -> print sum
handleBatch = getLine >> printBatchResult
handleAllBatches n = replicateM_ n handleBatch
main = readLn >>= \numberOfBatches -> handleAllBatches numberOfBatches
最后,添加签名:
sumLine :: String -> Int
sumLine line = (sum . map read . words) line
handleNumbersLine :: IO Int
handleNumbersLine = fmap sumLine getLine
printBatchResult :: IO ()
printBatchResult = handleNumbersLine >>= \sum -> print sum
handleBatch :: IO ()
handleBatch = getLine >> printBatchResult
handleAllBatches :: Int -> IO ()
handleAllBatches n = replicateM_ n handleBatch
main = readLn >>= \numberOfBatches -> handleAllBatches numberOfBatches
一些最终评论:
>>=
- 来自monad的bind
函数将一个monad转换为另一个(或相同的)并转换其值。在main
函数中,它需要IO Int
,转换lambda并返回IO ()
- 转换的结果,它是空的并在过程中打印结果。>>
- (在handleBatch
中使用)忽略左参数(一行中有多少个数字(可以说)是不必要的)并且只返回正确的参数 - 这是一个处理a的函数与数字对齐。