处理复杂的输入(使用嵌套值)

时间:2017-08-20 21:49:01

标签: haskell

有以下任务:

  1. 第一行是案件数
  2. 对于每种情况,都有一行包含要添加的数字
  3. 对于每种情况,还有一行数字
  4. 对于每种情况,我必须打印总和数
  5. 示例:

    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;它? :)

2 个答案:

答案 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的函数与数字对齐。