找到最大的n,使n! <k在Haskell中,使用折叠和无限级数

时间:2018-10-03 06:08:45

标签: haskell

在标题中说起来似乎很简单,但是我很难对其进行编码。我正在寻找使n大的n! <给定的k。

这是我尝试过的:

func1 = foldl (*) 1 [1..] . takeWhile (\x -> x < (read "1e100" :: Scientific ))

func2 = (\x -> foldl (*) 1 [1..x] . takeWhile (x < (read "1e100" :: Scientific )))

func3 = do
        forM_ [1..] $ \x -> do
            let y = foldl (*) 1 [1..x]
            when y >= (read "1e100" :: Scientific ) $
                putStrLn x
                return ()

func4 k = let nfac = foldl (*) 1 [1..n]
              where nfac > k
-- func4 (read "1e100" :: Scientific )

我正在使用Data.Scientific库,因为k通常会很大。

正确表达这句话的惯用方式是什么?

2 个答案:

答案 0 :(得分:2)

简短答案:将您的程序划分为各自执行专门任务的功能。

我们首先定义一个函数来计算阶乘:

fact :: (Num a, Enum a) => a -> a
fact x = foldl (*) 1 [1..x]

现在我们可以生成一个2元组的列表,其中第一项是i,第二项是i!

facts :: (Num a, Enum a) => [(a, a)]
facts = map (\i -> (i, fact i)) [1..]

现在我们可以使用takeWhile来过滤此列表,以仅返回第二项(因此i!)小于n的元组:

factsless :: (Num a, Enum a) => a -> [(a, a)]
factsless n = takeWhile (\(_, fi) -> fi < n) facts

现在,我们可以使用last获取此列表的 last 元组,然后使用fst获得相应的i

solution :: (Num a, Enum a) => a -> a
solution n = fst (last (factsless n))

鉴于n很大,只有Integer可以代表该数字。因此,将Integer用于a可能更安全,因为否则,小于检查将永远不会失败,因此会发生溢出。

例如:

Prelude> solution 2
1
Prelude> solution 3
2
Prelude> solution 4
2
Prelude> solution 5
2
Prelude> solution 6
2
Prelude> solution 7
3
Prelude> solution 10
3
Prelude> solution 100
4
Prelude> solution 10000
7 
Prelude> solution (10^100)
69 

由于阶乘是整数,因此最好避免浮点数,通常整数会更精确,紧凑和高效。

优化

我们可以通过生成无限列表(例如,使用scanl)来提高计算阶乘的性能:

facts :: (Num a, Enum a, Num b, Enum b) => [(a, b)]
facts = zip [1..] (scanl (*) 1 [2..])

可以进行其他优化,但我将其保留为练习。

答案 1 :(得分:2)

用扫描代替折叠,并通过takeWhile ...放入 结果。然后将其编入索引并使用last

但是首先解开read,将其转换为简单的参数值。对于您要实现的算法,它的来源无关紧要。因此

factProblem :: Integer -> Int
factProblem k = -- fst . last . zip [0..] 
                pred . length
                   . takeWhile (< k) . scanl (*) 1 $ [1..]

main :: IO ()
main = getLine >>= print . factProblem . read