【问题标题】:Making a list of divisors without dividing sequentially in Haskell在Haskell中制作除数列表而不按顺序划分
【发布时间】:2016-05-17 06:02:24
【问题描述】:

我在 Haskell 学习。

我一直在实现一个生成除数列表的函数。
我的第一个代码在这里:

代码:

divisors :: Integral a => a -> [a]
divisors n
  | n < 1 = []
  | otherwise = filter ((== 0) . (mod n)) [1..n]

此代码与Making a list of divisors in Haskell几乎相同。
它可以工作,但速度很慢。

我觉得除以[1..n]中的每一个都没有效率。
是否有另一种聪明的方法来制作除数列表?

更新:

在n &lt; 1的情况下,[1..n]与[]相同。
因此,根本不需要守卫:

divisors :: Integral a => a -> [a]
divisors n = filter ((== 0) . (mod n)) [1..n]

【问题讨论】:

    标签: haskell math factors


    【解决方案1】:

    强制性的一元代码:

    import Control.Monad
    
    divisors = map product . mapM (scanl (*) 1) . group . factors
    

    factors 与您的其他链接问题一样。

    您已经在使用 List monad 中的 sequence,无需显式调用 concatMap(mapM f 等效于 sequence . map f)。

    该列表不会被排序,就像您原来的按顺序划分的 O(n) 代码生成的那样 - 您可以将其设为 O(sqrt(n)) 顺便说一句,它有一个足够简单的技巧,但平均而言,它仍然比这段代码慢得多。

    【讨论】:

    • 我使用您的代码执行了与my comment 中注释相同的计算。需要 0.01 和 0.05 秒!
    【解决方案2】:

    我自己的实现现在使用幂集素数。

    例如,要得到30的除数列表,其中素数是[2,3,5],

    1. 制作素因数的幂集,[[],[2],[3],[5],[2,3],[2,5],[3,5],[2,3,5]]
    2. 产品每个元素并得到结果[1,2,3,5,6,10,15,30],这是30的除数列表

    代码:

    divisors :: Integral a => a -> [a]
    divisors n
      | n < 1 = []
      | otherwise = distinct $ map product $ (powerset . factors) n
    
    -- | remove duplicated element in a list
    distinct :: Eq a => [a] -> [a]
    distinct [] = []
    distinct (x : xs)
      | x `elem` xs = distinct xs
      | otherwise = x : distinct xs
    
    -- | generate power set of a list
    powerset :: [a] -> [[a]]
    powerset [] = [[]]
    powerset (x : xs) = xss ++ map (x :) xss where xss = powerset xs
    
    -- | generate prime factors of a integer
    factors :: Integral a => a -> [a]
    factors m = f m (head primes) (tail primes) where
      f m n ns
        | m < 2 = []
        | m < n ^ 2 = [m]
        | m `mod` n == 0 = n : f (m `div` n) n ns
        | otherwise = f m (head ns) (tail ns)
    
    -- | prime sequence
    primes :: Integral a => [a]
    primes = 2 : filter (\n-> head (factors n) == n) [3,5..]
    

    在此代码中,如果存在重复的素因数,则会出现重复的除数。
    我在最后一步删除了重复的除数,但它不能解决重复的基本原因。
    我确信还有更聪明的方法。

    注意:


    更新:

    感谢comingstorm's advice,我学习了更多关于Data.List 模块并更新了我的代码:

    import Data.List (group, subsequences)
    
    divisors :: Integral a => a -> [a]
    divisors = map product . concatMap sequence . subsequences . map (scanr1 (*)) . group . factors
    
    primes = ...    -- same as before
    
    factors m = ... -- same as before
    

    我注意到原始代码中的distinct 和powerset 与Data.List 模块中的nub 和subsequences 相同。
    它变得简单,但behzad.nouri's code 更快。

    【讨论】:

    • 如果存在重复的素因数,您可以计算它们,而不是将它们保留为重复。然后,您可以通过迭代每个素数的计数来避免重复的除数,而不是获取完整列表的幂集。
    • @comingstorm 我发现我的代码有问题。我修复了它并附加了更新的代码。谢谢你的建议。
    • behzad.nouri的代码使用所有数字作为候选;你的只是素数。应该更快吧?除了首先生成它们需要时间。您的代码仍然很慢——它使用了试用除法,而不是更快的 Eratosthenes 筛(查看包 arithmoi)。但至少在第二次运行时,可以使用相同的素数列表,之前已经计算过了,对吧?除非你给它一个单态类型的签名,比如[Int],或者[Integer]。但是你给了它多态类型,Integral a =&gt; [a]。所以它每次都重新计算! :)
    • @WillNess 我的更新代码需要 0.01 和 56.19 秒来计算 length $ divisors 260620460100 和 length $ divisors 1000000007。 Your comment 是我的代码运行缓慢的唯一原因。修复类型签名并使用Math.NumberTheory.Primes.Sieve 模块,它变得比behzad.nouri's 代码更快。
    • @HironobuNagaya Haskell 从明确指定的类型签名中获取指导​​。所以你需要知道你在做什么。如果写错了,可能会导致类型错误。没有它,它会猜测,然后如果一切都合适,你就有了开始的地方。 OTOH 有类型导向的开发,你从类型开始,它们帮助你的想法...... - 在这种情况下,你已经给factors签名,所以我的答案中divisors 的类型会被相应地推断出来,所以我没有打扰。类型签名很好,尤其是。在成品中。
    【解决方案3】:

    使用此代码:

    import Data.List (group)
    import Control.Arrow ((&&&))
    
    divisors n = foldr go [1] . map (head &&& length) . group $ fac n 2
        where
        go (_, 0) xs = xs
        go (p, k) xs = let ys = map (* p) xs in go (p, pred k) ys ++ xs
        fac n i
            | n < i * i      = if n == 1 then [] else [n]
            | n `mod` i == 0 = i: fac (n `div` i) i
            | otherwise      = fac n $ succ i
    

    你会得到:

    \> -- print timing/memory stats after each evaluation
    \> :set +s
    \> length $ divisors 260620460100
    2187
    (0.01 secs, 0 bytes)
    \> length $ divisors 1000000007  -- prime number
    2
    (0.08 secs, 13,678,856 bytes)
    

    您可以与您的实现进行比较。

    【讨论】:

    • 在我的电脑上,你的工具需要 0.01 和 0.26 秒才能完成length $ divisors 260620460100 和 length $ divisors 1000000007,而我的工具需要 1.30 和 57.12 秒。你的代码有多快!有一些功能我从来没有见过,现在我看不懂一半。我正在研究你的代码。非常感谢!
    • @HironobuNagaya 如果你想了解&amp;&amp;&amp; 做了什么see this answer
    • 我认为现在理解&amp;&amp;&amp;还为时过早,因为我无法理解官方文档的含义。但是您的回答对于基本的代码示例非常有启发性,我可以理解。再次感谢您!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-18
    • 1970-01-01
    相关资源
    最近更新 更多