检测Haskell中的循环行为

时间:2009-09-28 14:48:02

标签: haskell

我正在Haskell做另一个projecteuler问题,我必须找到一个数字中每个数字的阶乘的总和是否等于原始数字。如果不重复该过程,直到达到原始数字。接下来的部分是找出具有60个非重复单元的起始数低于100万。我到目前为止:

prob74 = length [ x | x <- [1..999999], 60 == ((length $ chain74 x)-1)]

factorial n = product [1..n]

factC x = sum $ map factorial (decToList x)

chain74 x  | x == 0 = []
           | x == 1 = [1]
           | x /= factC x = x : chain74 (factC x)

但我不知道怎么办,一旦x的值变成循环,就让它停止。当它回到原始号码时,我将如何停止chain74

4 个答案:

答案 0 :(得分:2)

当您浏览可能包含循环的列表时,您的函数需要跟踪已经看到的元素以便能够检查重复。将每个新元素与已经看到的元素进行比较。如果已经看到新元素,则循环完成,如果没有看到则检查下一个元素。

因此,这计算列表的非循环部分的长度:

uniqlength :: (Eq a) => [a] -> Int
uniqlength l = uniqlength_ l []
   where uniqlength_ [] ls = length ls
         uniqlength_ (x:xs) ls
            | x `elem` ls = length ls
            | otherwise   = uniqlength_ xs (x:ls)

(使用集合而不是列表时性能可能会更好,但我没有尝试过。)

答案 1 :(得分:1)

如何将另一个参数(例如y)传递给列表推导中的chain74。

早上失败所以编辑:

[.. ((length $ chain74 x x False)-1)]



chain74 x y not_first  | x == y && not_first = replace_with_stop_value_:-)
                       | x == 0 = []
                       | x == 1 = [1]
                       | x == 2 = [2]
                       | x /= factC x = x : chain74 (factC x) y True

答案 2 :(得分:1)

我在我的博客上在Haskell中实现了一个循环检测算法。它应该对你有用,但对于这个特殊问题可能有一个更聪明的方法:

http://coder.bsimmons.name/blog/2009/04/cycle-detection/

只需将返回类型从String更改为Bool。

编辑:以下是我发布的算法的修改版本:

cycling :: (Show a, Eq a) => Int -> [a] -> Bool
cycling k []     = False --not cycling
cycling k (a:as) = find 0 a 1 2 as
    where find _ _ c _  [] = False
          find i x c p (x':xs) 
            | c >  k    =  False  -- no cycles after k elements
            | x == x'   =  True   -- found a cycle 
            | c == p    = find c x' (c+1) (p*2) xs 
            | otherwise = find i x  (c+1) p xs

如果您知道列表将会循环或很快终止,您可以删除“k”。

EDIT2:您可以将以下功能更改为:

prob74 = length [ x | x <- [1..999999], let chain = chain74 x, not$ cycling 999 chain, 60 == ((length chain)-1)]

答案 3 :(得分:1)

非常有趣的问题。我想出了一个corecursive函数,它返回每个数字的“阶乘链”列表,一旦它们重复就会停止:

chains = [] : let f x = x : takeWhile (x /=) (chains !! factC x) in (map f [1..])

,并提供:

take 4 chains == [[],[1],[2],[3,6,720,5043,151,122,5,120,4,24,26,722,5044,169,363601,1454]]

map head $ filter ((== 60) . length) (take 10000 chains) 
is 
[1479,1497,1749,1794,1947,1974,4079,4097,4179,4197,4709,4719,4790,4791,4907,4917
,4970,4971,7049,7094,7149,7194,7409,7419,7490,7491,7904,7914,7940,7941,9047,9074
,9147,9174,9407,9417,9470,9471,9704,9714,9740,9741]

它的工作原理是计算列表中其位置的“factC”,然后引用该位置。这将生成无限列表的无限列表(使用延迟评估),但是使用takeWhile,内部列表只会继续,直到元素再次出现或列表结束(意味着corecursion中的更深层元素已经重复)。

如果您只想从列表中删除循环,可以使用:

decycle :: Eq a => [a] -> [a]
decycle = dc []
    where
        dc _ [] = []
        dc xh (x : xs) = if elem x xh then [] else x : dc (x : xh) xs

decycle [1, 2, 3, 4, 5, 3, 2] == [1, 2, 3, 4, 5]