在 Haskell 中不按顺序划分而制作除数列表

Making a list of divisors without dividing sequentially in Haskell

我在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 < 1的情况下,[1..n][]相同。
所以,根本不需要守卫:

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

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

例如,要获取 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
  3. 的除数列表

代码:

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..]

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

注:

  • primesfactors 引用自 Prime factors in Haskell
  • powerset 引用自 powerset

更新:

感谢,我研究了更多关于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

我注意到原始代码中的distinctpowersetData.List[=68中的nubsubsequences相同=]模块。
它变得简单,但 更快。

使用此代码:

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)

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

强制性单子代码:

import Control.Monad

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

factors 与您的其他相关问题一样。

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

虽然列表不会被排序,就像您最初的顺序划分 O(n) 代码生成的列表一样 — 您可以做到 O(sqrt(n)) 顺便说一句,有一个足够简单的技巧,尽管平均而言它仍然比这段代码慢得多。