-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprime_numbers.go
189 lines (163 loc) · 4.86 KB
/
prime_numbers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Copied and adapted from: tinyurl.com/gosieve
// https://youtu.be/f6kdp27TYZs
// A concurrent prime sieve
package maths
import (
"context"
"math"
)
// GetPrimeNumbersBelowAndIncluding fills a channel with the prime numbers below and including |n|, in order. Uses a euclidean sieve.
// Cancel the context when the calling function has finished with the return values and does not care about further possible values.
func GetPrimeNumbersBelowAndIncluding(ctx context.Context, n int) <-chan int {
// Special case when n is equal to math.MinInt.
// In this case, getting the absolute value would return an error, but the prime numbers below |math.MinInt| and math.MaxInt are the same.
// (i.e. math.MaxInt, 2⁶³ - 1, is not a prime itself).
if n == math.MinInt {
n = math.MaxInt
}
if n < 0 {
n = -n // Because math.MinInt case is checked above, this can not panic with an error.
}
primeChannel := make(chan int)
go func() {
defer close(primeChannel)
if n < 2 {
return
}
maxPrime := int(math.Sqrt(float64(n)))
// Step 1: All composite numbers below and including n must have a prime factor p such that p <= SQRT(n).
// Hence to find all composite numbers, and therefore all prime numbers, generate all primes up to SQRT(n).
smallPrimes := getPrimesUpTo(maxPrime)
// Split the range [2, n] into numSegments equal (or nearly equal) segments.
// Each segment is processed separately, reducing the maximum memory usage at any point.
// Memory usage is roughly proportional to n / numSegments.
// An easy way to implement the number of segments is to have it proportional (in this case set) to maxPrime.
numSegments := maxPrime
segmentSize := n / numSegments
for segment := 0; segment < numSegments; segment++ {
start := 2 + segment*segmentSize
var end int
if segment == numSegments-1 {
end = n + 1
} else {
end = start + segmentSize
}
// Step 2: Create a slice, isComposite, for the current segment to track whether numbers in this segment are composite.
isComposite := make([]bool, end-start)
// Step 3: Mark composites within the segment using smallPrimes
for _, p := range smallPrimes {
// Find the minimum number in [start, end) that is a multiple of p
minMultiple := ((start + p - 1) / p) * p
if minMultiple < p*p {
minMultiple = p * p
}
// Mark all multiples of p within the segment.
for j := minMultiple; j < end; j += p {
isComposite[j-start] = true
}
}
// Step 4: Iterate over isComposite and send any number that is not marked as composite (i.e., false) to primeChannel.
for i := 0; i < len(isComposite); i++ {
if !isComposite[i] {
select {
case primeChannel <- start + i:
case <-ctx.Done():
return
}
}
}
}
}()
return primeChannel
}
func getPrimesUpTo(n int) []int {
if n < 2 {
return []int{}
}
if n <= 5 {
switch n {
case 2:
return []int{2}
case 3:
return []int{2, 3}
case 4, 5:
return []int{2, 3, 5}
}
}
// Wheel factorization for 2, 3, 5.
wheel := []int{4, 2, 4, 2, 4, 6, 2, 6}
wIndex := 0
candidate := 7
// isComposite[i] represents whether candidate + i is composite.
isComposite := make([]bool, n+1)
primes := []int{2, 3, 5}
for candidate <= n {
if !isComposite[candidate] {
primes = append(primes, candidate)
// Mark multiples of candidate as composite
for j := candidate * candidate; j <= n; j += candidate {
isComposite[j] = true
}
}
candidate += wheel[wIndex]
wIndex = (wIndex + 1) % len(wheel)
}
return primes
}
// GetPrimeNumbers returns a channel from which to siphon off the prime numbers in order, as needed.
// Send a boolean to the Done channel when finished.
// The prime sieve: Daisy-chain Filter processes.
func GetPrimeNumbers() (<-chan int, chan<- bool) {
ch := make(chan int) // Create a new channel.
ctx, cancel := context.WithCancel(context.Background())
go generate(ctx, ch) // Launch Generate goroutine.
primeCh := make(chan int) // Create return channel.
doneCh := make(chan bool) // Create done channel.
go func() {
<-doneCh
cancel()
}()
go func() {
for {
prime := <-ch
select {
case primeCh <- prime:
case <-ctx.Done():
return
}
ch1 := make(chan int)
go filter(ctx, ch, ch1, prime)
ch = ch1
}
}()
return primeCh, doneCh
}
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ctx context.Context, ch chan<- int) {
for i := 2; ; i++ {
select {
case ch <- i: // Send 'i' to channel 'ch'.
case <-ctx.Done():
return
}
}
}
// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(ctx context.Context, in <-chan int, out chan<- int, prime int) {
for {
var i int
select {
case i = <-in: // Receive value from 'in'.
case <-ctx.Done():
return
}
if i%prime != 0 {
select {
case out <- i: // Send 'i' to 'out'.
case <-ctx.Done():
return
}
}
}
}