开发者

Skipping exceptions when using map in Haskell

开发者 https://www.devze.com 2022-12-18 14:11 出处:网络
I have the following code to return the length of a cycle in a string: module Main where import Data.List

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 Nothings?

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消