我尝试实现一个带限制和字符串的函数,解析字符串并测试解析后的数字是否超出限制。该函数仅适用于没有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调试器只能打印类型而不是值。
答案 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 <$ b
为b
,则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)
...我们不会在此代码中宣布任何错误,但这会让我们看到每一步发生了什么,也没有方向在折叠中,所以你不能在左右之间向后移动。