Skip to content

Commit fde566c

Browse files
authored
Merge pull request #124 from adrsm108/divisors
Divisors function
2 parents 560a9f4 + 458a2c4 commit fde566c

File tree

4 files changed

+122
-4
lines changed

4 files changed

+122
-4
lines changed

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Primes.primesmask
3434
```@docs
3535
Primes.radical
3636
Primes.totient
37+
Primes.divisors
3738
```

src/Primes.jl

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# This file includes code that was formerly a part of Julia. License is MIT: http://julialang.org/license
2-
32
module Primes
43

5-
using Base.Iterators: repeated
4+
using Base.Iterators: repeated, rest
65

76
import Base: iterate, eltype, IteratorSize, IteratorEltype
87
using Base: BitSigned
98
using Base.Checked: checked_neg
109
using IntegerMathUtils
1110

12-
export isprime, primes, primesmask, factor, eachfactor, ismersenneprime, isrieselprime,
11+
export isprime, primes, primesmask, factor, eachfactor, divisors, ismersenneprime, isrieselprime,
1312
nextprime, nextprimes, prevprime, prevprimes, prime, prodfactors, radical, totient
1413

1514
include("factorization.jl")
@@ -592,7 +591,7 @@ given by `f`. This method may be preferable to [`totient(::Integer)`](@ref)
592591
when the factorization can be reused for other purposes.
593592
"""
594593
function totient(f::Factorization{T}) where T <: Integer
595-
if !isempty(f) && first(first(f)) == 0
594+
if iszero(sign(f))
596595
throw(ArgumentError("ϕ(0) is not defined"))
597596
end
598597
result = one(T)
@@ -908,4 +907,90 @@ julia> prevprimes(10, 10)
908907
prevprimes(start::T, n::Integer) where {T<:Integer} =
909908
collect(T, Iterators.take(prevprimes(start), n))
910909

910+
"""
911+
divisors(n::Integer) -> Vector
912+
913+
Return a vector with the positive divisors of `n`.
914+
915+
For a nonzero integer `n` with prime factorization `n = p₁^k₁ ⋯ pₘ^kₘ`, `divisors(n)`
916+
returns a vector of length (k₁ + 1)⋯(kₘ + 1) containing the divisors of `n` in
917+
lexicographic (rather than numerical) order.
918+
919+
`divisors(-n)` is equivalent to `divisors(n)`.
920+
921+
For convenience, `divisors(0)` returns `[]`.
922+
923+
# Example
924+
925+
```jldoctest
926+
julia> divisors(60)
927+
12-element Vector{Int64}:
928+
1 # 1
929+
2 # 2
930+
4 # 2 * 2
931+
3 # 3
932+
6 # 3 * 2
933+
12 # 3 * 2 * 2
934+
5 # 5
935+
10 # 5 * 2
936+
20 # 5 * 2 * 2
937+
15 # 5 * 3
938+
30 # 5 * 3 * 2
939+
60 # 5 * 3 * 2 * 2
940+
941+
julia> divisors(-10)
942+
4-element Vector{Int64}:
943+
1
944+
2
945+
5
946+
10
947+
948+
julia> divisors(0)
949+
Int64[]
950+
```
951+
"""
952+
function divisors(n::T) where {T<:Integer}
953+
n = abs(n)
954+
if iszero(n)
955+
return T[]
956+
elseif isone(n)
957+
return [n]
958+
else
959+
return divisors(factor(n))
960+
end
961+
end
962+
963+
"""
964+
divisors(f::Factorization) -> Vector
965+
966+
Return a vector with the positive divisors of the number whose factorization is `f`.
967+
Divisors are sorted lexicographically, rather than numerically.
968+
"""
969+
function divisors(f::Factorization{T}) where {T<:Integer}
970+
sgn = sign(f)
971+
if iszero(sgn) # n == 0
972+
return T[]
973+
elseif isempty(f) || length(f) == 1 && sgn < 0 # n == 1 or n == -1
974+
return [one(T)]
975+
end
976+
977+
i = m = 1
978+
fs = rest(f, 1 + (sgn < 0))
979+
divs = Vector{T}(undef, prod(x -> x.second + 1, fs))
980+
divs[i] = one(T)
981+
982+
for (p, k) in fs
983+
i = 1
984+
for _ in 1:k
985+
for j in i:(i+m-1)
986+
divs[j+m] = divs[j] * p
987+
end
988+
i += m
989+
end
990+
m += i - 1
991+
end
992+
993+
return divs
994+
end
995+
911996
end # module

src/factorization.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,5 @@ Base.length(f::Factorization) = length(f.pe)
6565

6666
Base.show(io::IO, ::MIME{Symbol("text/plain")}, f::Factorization) =
6767
join(io, isempty(f) ? "1" : [(e == 1 ? "$p" : "$p^$e") for (p,e) in f.pe], " * ")
68+
69+
Base.sign(f::Factorization) = isempty(f.pe) ? one(keytype(f)) : sign(first(f.pe[1]))

test/runtests.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ end
236236

237237
@test factor(1) == Dict{Int,Int}()
238238

239+
# correctly return sign of factored numbers
240+
@test sign(factor(100)) == 1
241+
@test sign(factor(1)) == 1
242+
@test sign(factor(0)) == 0
243+
@test sign(factor(-1)) == -1
244+
@test sign(factor(-100)) == -1
245+
239246
# factor returns a sorted dict
240247
@test all([issorted(collect(factor(rand(Int)))) for x in 1:100])
241248

@@ -313,6 +320,29 @@ end
313320
end
314321
end
315322

323+
# brute-force way to get divisors. Same elements as divisors(n), but order may differ.
324+
divisors_brute_force(n) = [d for d in one(n):n if iszero(n % d)]
325+
326+
@testset "divisors(::$T)" for T in [Int16, Int32, Int64, BigInt]
327+
# 1 and 0 are handled specially
328+
@test divisors(one(T)) == divisors(-one(T)) == T[one(T)]
329+
@test divisors(zero(T)) == T[]
330+
331+
for n in 2:1000
332+
ds = divisors(T(n))
333+
@test ds == divisors(-T(n))
334+
@test sort!(ds) == divisors_brute_force(T(n))
335+
end
336+
end
337+
338+
@testset "divisors(::Factorization)" begin
339+
# divisors(n) calls divisors(factor(abs(n))), so the previous testset covers most cases.
340+
# We just need to verify that the following cases are also handled correctly:
341+
@test divisors(factor(1)) == divisors(factor(-1)) == [1] # factorizations of 1 and -1
342+
@test divisors(factor(-56)) == divisors(factor(56)) == [1, 2, 4, 8, 7, 14, 28, 56] # factorizations of negative numbers
343+
@test isempty(divisors(factor(0)))
344+
end
345+
316346
# check copy property for big primes relied upon in nextprime/prevprime
317347
for n = rand(big(-10):big(10), 10)
318348
@test n+0 !== n

0 commit comments

Comments
 (0)