Skip to content

Commit 7580f64

Browse files
committed
feat: add lazy prime iterator API (primes_from, primes_upto)
Add PrimeIterator{T, Up} unified parametric type with primes_from(n) for ascending and primes_upto(n) for descending lazy iteration over primes. Fully composable with Base.Iterators (take, takewhile, zip, etc.). Closes #176.
1 parent 20a92a0 commit 7580f64

4 files changed

Lines changed: 267 additions & 4 deletions

File tree

.gitignore

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,62 @@
1+
*
2+
3+
### Allowed files and directories ###
4+
5+
!.gitignore
6+
!.github/
7+
!.github/workflows/
8+
!.github/workflows/*.yml
9+
!.github/dependabot.yml
10+
11+
!LICENSE.md
12+
!README.md
13+
!Project.toml
14+
15+
!src/
16+
!src/*.jl
17+
18+
!test/
19+
!test/*.jl
20+
21+
!docs/
22+
!docs/src/
23+
!docs/src/*.md
24+
!docs/make.jl
25+
!docs/Project.toml
26+
27+
!benchmarks/
28+
!benchmarks/*.jl
29+
!benchmarks/Project.toml
30+
!benchmarks/README.md
31+
32+
### Denied even if allowed above ###
33+
34+
# Files generated by invoking Julia with --code-coverage
135
*.jl.cov
236
*.jl.*.cov
37+
38+
# Files generated by invoking Julia with --track-allocation
339
*.jl.mem
4-
Manifest.toml
5-
docs/build
6-
docs/site
40+
41+
# System-specific files and directories generated by the BinaryProvider and BinDeps packages
42+
# They contain absolute paths specific to the host computer, and so should not be committed
43+
deps/deps.jl
44+
deps/build.log
45+
deps/downloads/
46+
deps/usr/
47+
deps/src/
48+
49+
# Build artifacts for creating documentation generated by the Documenter package
50+
docs/build/
51+
docs/site/
752
docs/Manifest.toml
53+
54+
# File generated by Pkg, the package manager, based on a corresponding Project.toml
55+
# It records a fixed state of all packages used by the project. As such, it should not be
56+
# committed for packages, but should be committed for applications that require a static
57+
# environment.
58+
Manifest*.toml
59+
60+
# File generated by the Preferences package to store local preferences
61+
LocalPreferences.toml
62+
JuliaLocalPreferences.toml

docs/src/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Primes.primes
1919
Primes.nextprime
2020
Primes.prevprime
2121
Primes.prime
22+
Primes.primes_from
23+
Primes.primes_upto
24+
Primes.PrimeIterator
2225
```
2326

2427
## Identifying prime numbers

src/Primes.jl

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ using Base.Checked: checked_neg
99
using IntegerMathUtils
1010

1111
export isprime, primes, primesmask, factor, eachfactor, divisors, ismersenneprime, isrieselprime,
12-
nextprime, nextprimes, prevprime, prevprimes, prime, prodfactors, radical, totient
12+
nextprime, nextprimes, prevprime, prevprimes, prime, prodfactors, radical, totient,
13+
primes_from, primes_upto, PrimeIterator
1314

1415
include("factorization.jl")
1516

@@ -968,6 +969,117 @@ julia> prevprimes(10, 10)
968969
prevprimes(start::T, n::Integer) where {T<:Integer} =
969970
collect(T, Iterators.take(prevprimes(start), n))
970971

972+
"""
973+
PrimeIterator{T<:Integer, Up}
974+
975+
A lazy iterator over prime numbers. When `Up` is `true`, iterates in ascending order
976+
(constructed via [`primes_from`](@ref)). When `Up` is `false`, iterates in descending
977+
order (constructed via [`primes_upto`](@ref)).
978+
979+
The type parameter `T` determines the integer type of yielded primes, and `Up` is a
980+
compile-time boolean controlling iteration direction.
981+
"""
982+
struct PrimeIterator{T<:Integer, Up}
983+
start::T
984+
function PrimeIterator{T, Up}(start::T) where {T<:Integer, Up}
985+
start < zero(start) && throw(ArgumentError("start must be non-negative, got $start"))
986+
new{T, Up}(start)
987+
end
988+
end
989+
990+
function iterate(it::PrimeIterator{T, true}, state=it.start) where {T}
991+
p = nextprime(state)
992+
(p, add(p, one(p)))
993+
end
994+
995+
function iterate(it::PrimeIterator{T, false}, state=it.start) where {T}
996+
state < T(2) && return nothing
997+
p = prevprime(state)
998+
(p, p - one(p))
999+
end
1000+
1001+
IteratorSize(::Type{<:PrimeIterator{T, true}}) where {T} = Base.IsInfinite()
1002+
IteratorSize(::Type{<:PrimeIterator{T, false}}) where {T} = Base.SizeUnknown()
1003+
IteratorEltype(::Type{<:PrimeIterator}) = Base.HasEltype()
1004+
eltype(::Type{PrimeIterator{T, Up}}) where {T, Up} = T
1005+
1006+
function Base.show(io::IO, it::PrimeIterator{T, true}) where {T}
1007+
print(io, "primes_from(", it.start, ")")
1008+
end
1009+
1010+
function Base.show(io::IO, it::PrimeIterator{T, false}) where {T}
1011+
print(io, "primes_upto(", it.start, ")")
1012+
end
1013+
1014+
"""
1015+
primes_from(n::Integer)
1016+
1017+
Return a lazy, infinite iterator over all primes greater than or equal to `n`,
1018+
in ascending order.
1019+
1020+
# Examples
1021+
1022+
```jldoctest
1023+
julia> collect(Iterators.take(primes_from(10), 5))
1024+
5-element Vector{Int64}:
1025+
11
1026+
13
1027+
17
1028+
19
1029+
23
1030+
1031+
julia> first(primes_from(100))
1032+
101
1033+
```
1034+
"""
1035+
primes_from(n::T) where {T<:Integer} = PrimeIterator{T, true}(n)
1036+
1037+
"""
1038+
primes_from()
1039+
1040+
Return a lazy, infinite iterator over all primes starting from `2`.
1041+
Equivalent to `primes_from(2)`.
1042+
1043+
# Examples
1044+
1045+
```jldoctest
1046+
julia> collect(Iterators.take(primes_from(), 5))
1047+
5-element Vector{Int64}:
1048+
2
1049+
3
1050+
5
1051+
7
1052+
11
1053+
```
1054+
"""
1055+
primes_from() = primes_from(2)
1056+
1057+
"""
1058+
primes_upto(n::Integer)
1059+
1060+
Return a lazy iterator over all primes less than or equal to `n`,
1061+
in descending order. Terminates when no more primes remain.
1062+
1063+
# Examples
1064+
1065+
```jldoctest
1066+
julia> collect(primes_upto(20))
1067+
8-element Vector{Int64}:
1068+
19
1069+
17
1070+
13
1071+
11
1072+
7
1073+
5
1074+
3
1075+
2
1076+
1077+
julia> collect(primes_upto(1))
1078+
Int64[]
1079+
```
1080+
"""
1081+
primes_upto(n::T) where {T<:Integer} = PrimeIterator{T, false}(n)
1082+
9711083
"""
9721084
divisors(n::Integer) -> Vector
9731085

test/runtests.jl

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,99 @@ end
505505
@test Base.IteratorSize(prevprimes(10)) == Base.SizeUnknown()
506506
end
507507

508+
@testset "PrimeIterator - primes_from" begin
509+
@test first(primes_from(10)) == 11
510+
@test first(primes_from(11)) == 11
511+
@test collect(Iterators.take(primes_from(10), 4)) == [11, 13, 17, 19]
512+
513+
@test first(primes_from()) == 2
514+
@test collect(Iterators.take(primes_from(), 5)) == [2, 3, 5, 7, 11]
515+
516+
@test primes_from(10) isa PrimeIterator{Int, true}
517+
@test primes_from(Int32(10)) isa PrimeIterator{Int32, true}
518+
@test primes_from(big(10)) isa PrimeIterator{BigInt, true}
519+
520+
@test Base.IteratorSize(primes_from(10)) == Base.IsInfinite()
521+
522+
@test eltype(primes_from(10)) == Int
523+
@test eltype(primes_from(Int32(10))) == Int32
524+
@test eltype(primes_from(big(10))) == BigInt
525+
526+
@test collect(Iterators.take(primes_from(100), 3)) == [101, 103, 107]
527+
528+
@test collect(Iterators.takewhile(p -> p < 20, primes_from(10))) == [11, 13, 17, 19]
529+
530+
for n in [2, 3, 10, 100, 1000]
531+
@test first(primes_from(n)) == nextprime(n)
532+
end
533+
534+
@test repr(primes_from(10)) == "primes_from(10)"
535+
@test repr(primes_from(big(42))) == "primes_from(42)"
536+
537+
@test typeof(primes_from(10)) == PrimeIterator{Int, true}
538+
539+
@test_throws ArgumentError primes_from(-1)
540+
@test_throws ArgumentError primes_from(-10)
541+
542+
for n in [2, 5, 10, 100]
543+
for i in 1:5
544+
@test nextprime(n, i) == collect(Iterators.take(primes_from(n), i))[end]
545+
end
546+
end
547+
end
548+
549+
@testset "PrimeIterator - primes_upto" begin
550+
@test first(primes_upto(10)) == 7
551+
@test first(primes_upto(11)) == 11
552+
@test collect(Iterators.take(primes_upto(20), 4)) == [19, 17, 13, 11]
553+
554+
@test primes_upto(10) isa PrimeIterator{Int, false}
555+
@test primes_upto(Int32(10)) isa PrimeIterator{Int32, false}
556+
@test primes_upto(big(10)) isa PrimeIterator{BigInt, false}
557+
558+
@test Base.IteratorSize(primes_upto(10)) == Base.SizeUnknown()
559+
@test collect(primes_upto(10)) == [7, 5, 3, 2]
560+
@test collect(primes_upto(2)) == [2]
561+
562+
@test eltype(primes_upto(10)) == Int
563+
@test eltype(primes_upto(Int32(10))) == Int32
564+
@test eltype(primes_upto(big(10))) == BigInt
565+
566+
@test collect(Iterators.take(primes_upto(100), 3)) == [97, 89, 83]
567+
568+
pairs = collect(Iterators.take(Iterators.zip(primes_from(10), primes_upto(100)), 3))
569+
@test pairs == [(11, 97), (13, 89), (17, 83)]
570+
571+
for n in [2, 3, 10, 100, 1000]
572+
@test first(primes_upto(n)) == prevprime(n)
573+
end
574+
575+
@test repr(primes_upto(10)) == "primes_upto(10)"
576+
@test repr(primes_upto(big(42))) == "primes_upto(42)"
577+
578+
@test_throws ArgumentError primes_upto(-1)
579+
@test_throws ArgumentError primes_upto(-10)
580+
581+
@test collect(primes_upto(1)) == []
582+
@test collect(primes_upto(0)) == []
583+
584+
big_primes = collect(Iterators.take(primes_upto(big(100)), 3))
585+
@test big_primes == [prevprime(big(100), i) for i in 1:3]
586+
end
587+
588+
@testset "PrimeIterator - overflow propagation" begin
589+
@test_throws OverflowError collect(Iterators.take(primes_from(typemax(Int) - 1), 2))
590+
end
591+
592+
@testset "PrimeIterator - existing API unchanged" begin
593+
@test nextprime(10) == 11
594+
@test nextprime(10, 3) == 17
595+
@test prevprime(10) == 7
596+
@test prevprime(10, 3) == 3
597+
@test nextprimes(10, 3) == [11, 13, 17]
598+
@test prevprimes(10, 3) == [7, 5, 3]
599+
end
600+
508601
@testset "primes with huge arguments" begin
509602
if Base.Sys.WORD_SIZE == 64
510603
@test primes(2^63-200, 2^63-1) == [9223372036854775643, 9223372036854775783]

0 commit comments

Comments
 (0)