我有以下代码来返回字符串中循环的长度:
module Main where
import Data.List
detec ys n | 2*n > (length ys) = error "no cycle"
| t == h = (2*n - n)
| otherwise = detec ys (n+1)
where
t = ys !! n
h = if n == 0 then ys !! 1 else ys !! (n*2)
f x = detec (show x) 0
answer = map f [1/x|x<-[1..100]]
但是我不知道怎么做是让它忽略"no cycle"
异常,这样生成的列表只包含循环字符串的长度。
我该怎么做?
答案 0 :(得分:23)
请不要使用error
来实现预计会出现“错误”结果的逻辑。
相反,为什么不返回Maybe n
而不仅仅n
,然后use catMaybes
来过滤掉Nothing
?
这些变化很容易:
module Main where
import Data.List
import Data.Maybe
detec ys n | 2*n > (length ys) = Nothing
| t == h = Just (2*n - n)
| otherwise = detec ys (n+1)
where
t = ys !! n
h = if n == 0 then ys !! 1 else ys !! (n*2)
f x = detec (show x) 0
answer = catMaybes $ map f [1/x|x<-[1..100]]
顺便说一下,你是在列表的末尾索引;也许你打算检查2*n + 1 > length ys
?稍微偏离主题,我想提一下,!!
和length
在应用于列表时,大多数情况下都是低效和非惯用的,尤其是在像这样的迭代构造中。列表类型基本上是 cons单元列表,它是一种本质上递归的数据结构,并且强调不数组。理想情况下,您应该避免使用无法使用模式匹配轻松表达的列表执行任何操作,例如f (x:xs) = ...
。
答案 1 :(得分:5)
您正在使用Project Euler #26吗?
仅仅因为某个数字重复,并不意味着你找到了一个循环:例如,在334/999 = 0.334334334 ......中,循环不是(3),它是(334)。另外,依靠浮点计算来给你足够准确的数字是不明智的:#26的解决方案绝对超出了浮点数给你的范围。
在任何情况下,都有更简单的方法来查找周期。这会保留以前看到的元素列表,当它在列表中向下走时,并在发现当前元素已经被看到时返回。
findCycle :: Eq a => [a] -> Int
findCycle = findCycle' [] where
findCycle' _ [] = 0
findCycle' k (x:xs) = maybe (findCycle' (x:k) xs) succ $ elemIndex x k
您的tortoise and hare算法不完整:可能无法始终找到最小的周期。这个缺陷在这里得到纠正。
findCycle :: Eq a => [a] -> Int
findCycle xs = findCycle' xs (tail xs) where
findCycle' (x:xs) (y:_:ys)
| x == y = fromJust (elemIndex x xs) + 1
| otherwise = findCycle' xs ys
这假定它永远不会从列表末尾运行。如果您实现自己的十进制扩展,则可以轻松确保为真。
答案 2 :(得分:3)
您可以使用Control.Exception中的catch
,如
import Prelude hiding (catch)
import Control.Exception
main = do
print answer `catch` errorMessage
where
errorMessage :: SomeException -> IO ()
errorMessage = putStrLn . ("error: " ++) . show
捕捉SomeException
很草率,输出很乱:
[error: No cycle
它打印了一个数组,但遇到了异常。不是很好。
另一个答案涵盖了使用Maybe
monad表示可能失败的计算的精细方法。更通用的方法是MonadError
:
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative
import Control.Monad.Error
detec2 :: (MonadError String m, Eq a) => [a] -> Int -> m Int
detec2 ys n | 2*n >= (length ys) = throwError "No cycle"
| t == h = return (2*n - n)
| otherwise = detec2 ys (n+1)
where
t = ys !! n
h = if n == 0 then ys !! 1 else ys !! (n*2)
(请注意,这也解决了第一个警卫中允许!!
抛出异常的错误。)
这允许类似但更灵活的使用,例如:
answer2 = f2 <$> [1/x | x <- [1..100]]
f2 x = detec2 (show x) 0
main = do
forM_ answer2 $
\x -> case x of
Left msg -> putStrLn $ "error: " ++ msg
Right x -> print x
现在输出的前几行是
error: No cycle error: No cycle 2 error: No cycle error: No cycle 3 6 error: No cycle 2
请记住,这仍然是一个纯粹的功能:您不必在IO
内运行它。要忽略无周期错误,可以使用
cycles :: [Int]
cycles = [x | Right x <- answer2]
如果您根本不关心错误消息,则不要生成它们。一种自然的方法是使用列表返回空列表中没有循环,并使用concatMap
压缩结果:
detec3 :: (Show a) => a -> [Int]
detec3 x = go 0
where go :: Int -> [Int]
go n
| 2*n >= len = []
| t == h = [2*n - n]
| otherwise = go (n+1)
where t = ys !! n
h | n == 0 = ys !! 1
| otherwise = ys !! (n*2)
len = length ys
ys = show x
main = do
print $ concatMap (detec3 . recip) [1..100]
最后,您可能有兴趣阅读8 ways to report errors in Haskell。