Пропуск исключений при использовании карты в Haskell - PullRequest
3 голосов
/ 28 января 2010

У меня есть следующий код для возврата длины цикла в строке:

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", чтобы созданный список содержал только длины строк, которые являются циклическими.

Как я могу это сделать?

Ответы [ 3 ]

23 голосов
/ 28 января 2010

Пожалуйста, не используйте error для реализации логики, в которой ожидается "ошибочный" результат.

Вместо этого, почему бы не вернуть Maybe n вместо n, тогда используйте 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) = ....

5 голосов
/ 28 января 2010

Вы работаете над Project Euler # 26 ?

То, что определенная цифра повторяется, не означает, что вы нашли цикл: например, в 334/999 = 0.334334334 & hellip; цикл не (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

Ваш алгоритм черепахи и зайца неполон: он не всегда может найти наименьший цикл. Этот недостаток здесь исправлен.

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

Предполагается, что он никогда не выйдет за пределы списка. Если вы реализуете собственное десятичное расширение, вы можете легко убедиться, что это правда.

3 голосов
/ 28 января 2010

Вы можете использовать catch из Control.Exception, как в

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 для представления вычислений, которые могут потерпеть неудачу. Еще более общий подход - 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 способов сообщения об ошибках в Haskell .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...