Life Goes On

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

74問目

http://projecteuler.net/index.php?section=problems&id=74
ある数に対して、各桁の数字の階乗の和を求めていくとき、どんな数でも無限ループとなるが、繰り返し部分を除いた鎖の長さがちょうど 60 となるような数は百万未満でいくつあるか求める。
最初に書いたコード。コンパイルして 2 分かかります。

import Data.Char
import Data.List

main = print $ euler074 60 999999

euler074 :: Int -> Int -> Int
euler074 n m = length $ filter ((== n) . len . chain) [1..m]

len :: [Int] -> Int
len xs = length $ fst $ head $ dropWhile (\ (ys, z:zs) -> notElem z ys)
     [ splitAt n xs | n <- [1..] ]

chain :: Int -> [Int]
chain n = iterate f n
    where f = sum . map ((facts !!) . digitToInt) . show
          facts = scanl (*) 1 [1..]

階乗の和を求めるときは、どんな数字があるかが問題であって、それぞれの数字の順番はどうでもいいよね、ということがフォーラムにあって、それで調べる数字を絞り込んだのが↓のバージョン。題意を満たす数字の組合せを求めてから、それぞれの組合せに対する順列の個数を求めています。
確かに速くはなったのですが、0が含まれる場合とかを考慮すると、ちょっとコードが長くなってしまいました。
う〜ん、もう一息。

import Data.Char
import Data.List

main = print $ euler074 60 6

euler074 :: Int -> Int -> Int
euler074 n m = sum $ map (length . filter (\ (x:xs) -> x /= 0) . perm)
    $ filter ((== n) . len . chain) $ concat $ map (comb [0..9]) [1..m]

perm :: [Int] -> [[Int]]
perm [] = [[]]
perm xs = nub [ x : xs' | x <- xs, xs' <- perm (delete x xs) ]

len :: [[Int]] -> Int
len xs = length $ fst $ head $ dropWhile (\ (ys, z:zs) -> notElem z ys)
     [ splitAt n xs | n <- [1..] ]

chain :: [Int] -> [[Int]]
chain n = iterate f n
    where f = map digitToInt . show . sum . map (facts !!)
          facts = scanl (*) 1 [1..]

comb :: [Int] -> Int -> [[Int]]
comb _ 0 = [[]]
comb xs m = [ x : xs' | x <- xs, xs' <- comb (filter (>= x) xs) (m - 1) ]