3

I am studying in Haskell.

I have been implementing a function making a list of divisors.
My first code is here:

Code:

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

This code is almost same as Making a list of divisors in Haskell.
It works but slow.

I feel it is not efficient to divide by each of [1..n].
Is there another smart way to making a list of divisors?

Update:

In the case of n < 1, [1..n] is the same as [].
So, guard is not required at all:

divisors :: Integral a => a -> [a]
divisors n = filter ((== 0) . (mod n)) [1..n]
Community
  • 1
  • 1
Hironobu Nagaya
  • 127
  • 1
  • 10

3 Answers3

4

With this code:

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

you will get:

\> -- 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)

you may compare with your implementation.

behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
  • 1
    On my PC, your implements takes 0.01 and 0.26 secs to complete `length $ divisors 260620460100` and `length $ divisors 1000000007` while my implements takes 1.30 and 57.12 secs. How fast your code is! There are some functions I have never seen and I can not understand half of it now. I am studing with your code. Thank you very much! – Hironobu Nagaya Feb 08 '16 at 12:57
  • 1
    @HironobuNagaya if you want to understand what `&&&` does [see this answer](http://stackoverflow.com/a/32550390/625914) – behzad.nouri Feb 09 '16 at 01:09
  • 1
    I thought it was too early for me to understand `&&&` because I could not understood what official document meant. But your answer is very instructive with essential code examples and I can understand. Thank you again! – Hironobu Nagaya Feb 09 '16 at 15:51
3

My own implementation uses power set of prime factors now.

For example, to get list of divisors of 30, which prime factor is [2,3,5],

  1. make the power set of prime factors, [[],[2],[3],[5],[2,3],[2,5],[3,5],[2,3,5]]
  2. product each elements and get result [1,2,3,5,6,10,15,30], which is list of divisors of 30

Code:

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

In this code, duplicated divisors appear if duplicated prime factors exist.
I remove duplicated divisors at last step, but it does not fix basic cause of duplication.
I feel sure that there are more smart ways.

Note:


Update:

Thanks to comingstorm's advice, I studied more about Data.List module and update my code:

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

I noticed that distinct and powerset in original code are same with nub and subsequences in Data.List module.
It becomes simple but behzad.nouri's code is much faster.

Community
  • 1
  • 1
Hironobu Nagaya
  • 127
  • 1
  • 10
  • 1
    If duplicated prime factors exist, you can count them rather than keep them as duplicates. Then, you can avoid duplicate divisors by iterating over the count of each prime, rather than taking the powerset of the full list. – comingstorm Feb 07 '16 at 18:41
  • @comingstorm I recognized problem on my code. I fixed it and appended updated code. Thank you for your advice. – Hironobu Nagaya Feb 08 '16 at 14:48
  • and how fast are `length $ divisors 260620460100` and `length $ divisors 1000000007` on your computer now? :) – Will Ness Feb 10 '16 at 14:33
  • 1
    `behzad.nouri`'s code uses all numbers as candidates; yours just primes. Should be faster, right? Except it takes time to generate them first. Your code is still slow -- it uses trial division, not the sieve of Eratosthenes which can be much faster (check out the package *arithmoi*). But at least for the 2nd run, the same list of primes can be used, that was already calculated before, right? Not unless you give it a *monomorphic* type signature, like `[Int]`, or `[Integer]`. But you gave it *polymorphic* type, `Integral a => [a]`. So it *is recalculated* each time! :) – Will Ness Feb 10 '16 at 14:46
  • 1
    @WillNess My update code takes 0.01 and 56.19 secs to calclate `length $ divisors 260620460100` and `length $ divisors 1000000007`. [Your comment](http://stackoverflow.com/questions/35255007/making-a-list-of-divisors-without-dividing-sequentially-in-haskell/35255008?noredirect=1#comment58345235_35255008) is the just reason why my code is slow. Fixing type signature and using `Math.NumberTheory.Primes.Sieve` module, it became faster than `behzad.nouri's` code. – Hironobu Nagaya Feb 11 '16 at 06:26
  • @WillNess Is it better not to use type signature? I think defining type signature is a good manner because type signature is a important concept on Haskell, but most of Haskell answers does not use type signature. – Hironobu Nagaya Feb 11 '16 at 06:46
  • 1
    @HironobuNagaya Haskell takes guidance from the explicitly specified type signature. So you need to know what you're doing. If you write it wrong, you can cause type errors. Without it, it guesses, and then you have something to start from, if everything fits. OTOH there's type-directed development, where you *start* from types, they *aid* your thinking... -- In this case, you already gave the signature to `factors`, so the type for `divisors` in my answer gets deduced correspondingly, so I didn't bother. Type signatures are good, esp. in the *finished* product. – Will Ness Feb 11 '16 at 08:01
  • @WillNess My Haskell textbook recommends top-down design development, defining type signatures at first and writing function codes later. I know it is type-directed development now. I thought it is absolutely good to define type signatures. But I understand I can avoid writing it if obviously unneeded. Thank you! – Hironobu Nagaya Feb 12 '16 at 12:45
  • I think it's better to always have explicit type signature for every module-level definition, especially in the finished code. Here in the answers we're sometimes a bit careless and hasty. :) (not the best of us) :) – Will Ness Feb 12 '16 at 13:56
2

The obligatory monadic code:

import Control.Monad

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

The factors as in your other linked question.

You are already using sequence from List monad, no need to invoke concatMap explicitly (mapM f is equivalent to sequence . map f).

The list won't be ordered though, like the one produced by your original sequentially-dividing O(n) code is — you could make it O(sqrt(n)) with a simple enough trick by the way, though it would still be much slower than this code, on average.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I executed the same calculation as commented in [my comment](http://stackoverflow.com/questions/35255007/making-a-list-of-divisors-without-dividing-sequentially-in-haskell/35255729#comment58251630_35255729) with your code. It takes 0.01 and 0.05 secs! – Hironobu Nagaya Feb 11 '16 at 06:29