尝试添加两个数字时的算术错误

时间:2017-04-18 14:50:39

标签: haskell

我尝试实现一个带限制和字符串的函数,解析字符串并测试解析后的数字是否超出限制。该函数仅适用于没有0的字符串,如"123"。但是,它无法正确解析字符串,例如"100",其结果为1

是什么导致了这个问题?

以下是代码。

reachBounded :: Int -> String -> Maybe Int 
reachBounded limit str = case str of 
    ""  -> Nothing 
    "0" -> Just 0
    _   -> foldr (\digit s -> do 
                sum <- s
                let n = sum * 10 + digitToInt digit 
                guard (isDigit digit) 
                guard (n <= limit) 
                return n)
            (Just 0) str

此外,有没有像我们通常在命令式语言中那样调试此代码的方法?我发现ghci调试器只能打印类型而不是值。

1 个答案:

答案 0 :(得分:4)

这是解决问题的一种非常必要的方法,如果你继续这样思考,你将会遇到困难。

以下是您可能想要重新思考问题的方法:

替换&#34; 我有一个字符列表,但我想要数字,我会迭代并逐个替换它们&#34;使用&#34; 我有一个字符列表,但我想要数字,我只需要立即替换它们&#34; (我将假设您想要完全手动解析字符串,而不是仅仅使用read或某种解析工具)

到目前为止,我们已经:

reachBounded limit str = ... map digitToInt str

接下来,您要将这些数字转换为数字。替换&#34; 我想迭代这个列表增加一笔总和&#34;使用&#34; 我需要知道每个数字的位置值&#34;。我们可以通过反转数字并将它们与列表[1,10,100,1000 ...]成对相乘来实现。我们可以通过在正整数列表上映射(10 ^)来生成位值列表,或者声明每个元素是之前的10倍,从1开始。让我们使用后者:

reachBounded limit str = ... zipWith (*) (iterate (*10) 1) $ reverse $ map digitToInt str

我们想要这些地方值的总和:

reachBounded limit str = ... where
    val = sum $ zipWith (*) (iterate (*10) 1) $ reverse $ map digitToInt str

最后,我们必须检查它是否在给定的范围内:

reachBounded limit str = val <$ guard (val < limit) where
    val = sum $ zipWith (*) (iterate (*10) 1) $ reverse $ map digitToInt str

在这种情况下,如果a <$ bb,则a会将b的内容替换为Just something,如果b则将其留空是Nothing

在调试方面,它现在是微不足道的,因为它不是我们需要中断的一些过程,而是我们操作以获得所需结果的一系列值。你不能在每一步中运行你的部分过程并获得合理的答案,但在这里我们可以看看这些阶段产生的结果,看看我们是否正常。

没有toMaybe :: (a -> Bool) -> a -> Maybe a功能。我不确定为什么,但有一个并且使用read,解决方案仅仅是:

bounded l = toMaybe (<l) . read

或使用Safe库...

bounded l = toMaybe (<l) <=< readMay

如果您不输入实际代表数字的字符串,则不会抛出异常。

现在,让我们说你真的想要迭代地编写你的算法,也许你需要为了性能,或者它只是那些不容易承认声明性实现的算法之一(但是,其中很多都没有。使用值而不是异常仍然会更清晰,但有时你需要停下来看看它...那么你做什么?

让我们编写自己的迭代器函数:

data Iter a b c = Next a | Final b | Error c

iterateE :: (a -> Iter a b c) -> a -> ([a], Either c b)
iterateE f = go where
    go x = case f x of
        Next a -> let (list, final) = go a in (x:list, final)
        Final b -> ([x], Right b)
        Error c -> ([x], Left c)

这更直接地封装了提前停止折叠并跟踪中间结果 - 即使您也可以提前停止折叠并跟踪中间结果 - 这是现在考虑它的一种更简单的方法。这将为您提供所有中间状态的完整列表,以及迭代器函数可以选择终止的结果或错误。

将您的解决方案转换为此格式......

reachBounded limit str = iterateE iter (Just 0,str) where
    iter (n, []) = Final n
    iter (n, (s:str)) = Next (do
        sum <- s
        let n = sum * 10 + digitToInt digit 
        guard (isDigit digit) 
        guard (n <= limit) 
        return n, str)

...我们不会在此代码中宣布任何错误,但这会让我们看到每一步发生了什么,也没有方向在折叠中,所以你不能在左右之间向后移动。