Life Goes On

まあまあだけど楽しんでる方です

26問目

http://projecteuler.net/index.php?section=problems&id=26
1000未満の数dに対して1/dを循環小数で表したときに循環する部分の長さの最大値を求める。
下のコードが最初に書いたものです。間違いはないし処理時間も問題ないのですが、明示的に再帰しているところがHaskellっぽくないなぁと思って、別の書き方に挑戦しました。

main = print $ euler026 999

euler026 :: Int -> Int
euler026 n = (+ 1) $ length $ takeWhile (< (maximum lens)) lens
    where lens = map (cycleLength [1]) [1..n]

cycleLength :: [Int] -> Int -> Int
cycleLength rems n
    | (rem == 0) = 0
    | (elem rem rems) = (length $ takeWhile (/= rem) rems) + 1
    | otherwise = cycleLength (rem : rems) n
    where rem = mod ((head rems) * 10) n

で、できたのが下のコード。
一応動くのですが、無限リストから循環する部分を取り出すロジック(cyclePart)がいまいち複雑で、処理時間も上のコードより長くかかります。
もっと簡潔で、速く動くコードが書けないものでしょうか。
思いついた方は、ぜひコメントください。

main = print $ euler026 999

euler026 :: Int -> Int
euler026 n = (+ 1) $ length $ takeWhile (< (maximum lens)) lens
  where lens = map cycleLength [1..n]

cycleLength :: Int -> Int
cycleLength n = length $ cyclePart $ rems n

cyclePart :: [Int] -> [Int]
cyclePart xs = dropPrefix $ dropSuffix xs
  where
    dropPrefix xs
      | (last xs == 0) = []
      | otherwise = init $ dropWhile (/= last xs) xs
    dropSuffix xs = head $ dropWhile notDuplicate $ map (flip take xs) [1..]
    notDuplicate xs = notElem (last xs) (init xs)

rems :: Int -> [Int]
rems n = iterate next 1
  where next rem = mod (rem * 10) n

コメントでいいアルゴリズムを教えていただいたので、修正したものを載せておきます。詳しい説明はこちら。→どう書く?org 6144
文句無しに最速です。

cyclePart :: [Int] -> [Int]
cyclePart xs
  | (t' == 0) = []
  | otherwise = t' : (takeWhile (/= t') $ fst $ unzip ys)
  where
    ((t', r') : ys) = dropWhile (\ (t, r) -> t /= r) $
      zip xs $ map head $ iterate (drop 2) $ tail xs