I have the following code to return the length of a cycle in a string:
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]]
But what I don't know how to do is make it ignore the "no cycle"
exception so that the list produced only contains the lengths of the strings which are cyclic.
How can I do this?
Please don't use error
to implement logic where the "erroneous" result is expected to occur.
Instead, why not return Maybe n
instead of just n
, then use catMaybes
to filter out the Nothing
s?
The changes are easy enough:
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]]
By the way, you're indexing past the end of the list; perhaps you meant to check 2*n + 1 > length ys
? Drifting slightly off topic, I'd like to mention that !!
and length
are, for the most part, both inefficient and non-idiomatic when applied to lists, especially in an iterating construct like this. The list type is basically a cons cell list, which is an intrinsically recursive data structure, and is emphatically not an array. Ideally you should avoid doing anything with a list that can't be easily expressed with pattern matching, e.g., f (x:xs) = ...
.
Are you working on Project Euler #26?
Just because a certain digit repeats, doesn't mean you've found a cycle: for example, in 334/999=0.334334334…, the cycle isn't (3), it is (334). Also, it unwise to depend on floating point computation to give you enough accurate digits: the solution to #26 definitely goes beyond what floating-point will give you.
In any case, there are easier ways to find cycles. This keeps a list of previously-seen elements as it walks down the list, and returns when it finds that the current element has already been seen.
findCycle :: Eq a => [a] -> Int
findCycle = findCycle' [] where
findCycle' _ [] = 0
findCycle' k (x:xs) = maybe (findCycle' (x:k) xs) succ $ elemIndex x k
Your tortoise and hare algorithm is incomplete: it may not always find the smallest cycle. That flaw is corrected here.
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
This assumes that it'll never run off the end of the list. If you implement your own decimal expansion, you can easily ensure that is true.
You might use catch
from Control.Exception as in
import Prelude hiding (catch)
import Control.Exception
main = do
print answer `catch` errorMessage
where
errorMessage :: SomeException -> IO ()
errorMessage = putStrLn . ("error: " ++) . show
Catching SomeException
is sloppy, and the output is messy:
[error: No cycle
It got partway through printing an array but ran into the exception. Not very nice.
Another answer has covered the fine approach of using the Maybe
monad for representing computations that can fail. An even more general approach is 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)
(Notice this also fixes the bug in your first guard that allows !!
to throw exceptions.)
This permits similar but more flexible use, for example:
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
Now the first few lines of the output are
error: No cycle error: No cycle 2 error: No cycle error: No cycle 3 6 error: No cycle 2
Keep in mind this is still a pure function: you don't have to run it inside IO
. To ignore the no-cycle errors, you might use
cycles :: [Int]
cycles = [x | Right x <- answer2]
If you don't care about errors messages at all, then don't generate them. A natural way to do this is with lists where you return the empty list for no cycles and condense the result with 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]
Finally, you may be interested in reading 8 ways to report errors in Haskell.
精彩评论