在 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]
、
- 制作素数的幂集,
[[],[2],[3],[5],[2,3],[2,5],[3,5],[2,3,5]]
- 乘积每个元素并得到结果
[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..]
在此代码中,如果存在重复的质因数,则会出现重复的除数。
我在最后一步删除了重复的除数,但它没有解决重复的基本原因。
我确信还有更多聪明的方法。
注:
primes
和 factors
引用自 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
我注意到原始代码中的distinct
和powerset
与Data.List[=68中的nub
和subsequences
相同=]模块。
它变得简单,但 更快。
使用此代码:
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
,无需显式调用 concatMap
(mapM f
等同于 sequence . map f
)。
虽然列表不会被排序,就像您最初的顺序划分 O(n) 代码生成的列表一样 — 您可以做到 O(sqrt(n)) 顺便说一句,有一个足够简单的技巧,尽管平均而言它仍然比这段代码慢得多。
我在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]
、
- 制作素数的幂集,
[[],[2],[3],[5],[2,3],[2,5],[3,5],[2,3,5]]
- 乘积每个元素并得到结果
[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..]
在此代码中,如果存在重复的质因数,则会出现重复的除数。
我在最后一步删除了重复的除数,但它没有解决重复的基本原因。
我确信还有更多聪明的方法。
注:
primes
和factors
引用自 Prime factors in Haskell。powerset
引用自 powerset。
更新:
感谢
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[=68中的nub
和subsequences
相同=]模块。
它变得简单,但
使用此代码:
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
,无需显式调用 concatMap
(mapM f
等同于 sequence . map f
)。
虽然列表不会被排序,就像您最初的顺序划分 O(n) 代码生成的列表一样 — 您可以做到 O(sqrt(n)) 顺便说一句,有一个足够简单的技巧,尽管平均而言它仍然比这段代码慢得多。