patternkotlinMinor
Postponed Prime Sequence in Kotlin
Viewed 0 times
primepostponedkotlinsequence
Problem
Following my previous unbounded prime generator and a followup by Martin R, I've tested the waters in Kotlin by making an unbounded sieve.
To quote Martin R's wonderful explanation of the base algorithm,
The interesting thing about that algorithm is that the prime generator needs a list of "base primes" which are created using another instance of itself. This works because
so that the creation of nested prime generators terminates eventually.
The sieving is done using a dictionary which holds the "next composite numbers" which are divisible by any prime less than the current base prime. As explained [on SO], the required memory to produce \$ n \$ primes is \$ O(\sqrt n). \$
To quote Martin R's wonderful explanation of the base algorithm,
The interesting thing about that algorithm is that the prime generator needs a list of "base primes" which are created using another instance of itself. This works because
- the base prime generator is created "delayed", after producing some fixed primes, and
- in order to generate primes up to \$ N \$, base primes are needed only up to \$ \sqrt N \$
so that the creation of nested prime generators terminates eventually.
The sieving is done using a dictionary which holds the "next composite numbers" which are divisible by any prime less than the current base prime. As explained [on SO], the required memory to produce \$ n \$ primes is \$ O(\sqrt n). \$
class PostponedPrimeSequence() : AbstractIterator(), Sequence {
override fun iterator() = this
private var basePrimes: PostponedPrimeSequence? = null
private var basePrime = 0
private var sieve = mutableMapOf()
private var initialPrimes = mutableListOf(2, 3, 5, 7)
private var candidate = 9
override fun computeNext() {
if (!initialPrimes.isEmpty()) {
setNext(initialPrimes.removeAt(0))
} else {
if (candidate == 9) {
basePrimes = PostponedPrimeSequence()
basePrimes!!.next()
basePrime = basePrimes!!.next()
assert(candidate == basePrime * basePrime)
}
while (candidate > 0) {
val factor = sieve.remove(candidate) ?:
if (candidate == basePrime * basePrime) {
val temp = basePrime
basePrime = basePrimes!!.next()
temp
} else {
assert(candidate
Usage:
PostponedPriSolution
- You can make
basePrimesalateinitnon-nullvarto avoid!!.
sieveandinitialPrimesare only assigned once so they can be marked withvalinstead ofvar
sequenceOf(2, 3, 4, 7).iterator()can be used along withIterator.hasNextandIterator.nextinstead ofmutableListOf(2, 3, 5, 7)with somewhat difficult to read!initialPrimes.isEmpty()andinitialPrimes.removeAt(0)(although if you really prefer to use a collection instead of an iterator then you can useisNotEmptyinstead of!collection.isEmpty).
- Instead of explicitly declaring a
tempvariable you can useapplyto take the current value of a variable, use/change it, and return the initial value.
- As an alternative to
sieve.containsKey(j)you can usej in sieve.
- As an alternative to
candidate += 2 } candidate += 2you can move the latter+= 2to relatively the beginning ofcomputeNext(requires a few other changes, see below). The namecandidatewould no longer really fit though, but I think a generic property name likevalueis sufficient enough (we know what "value" we care about so we know what we're computing as we go, etc.).
- You probably shouldn't implement
IteratorandSequencein the same object. e.g. When invokingwith(PostponedPrimeSequence()) { repeat(2) { take(20).forEach(::println) } }I would expect it as aSequenceto print the first 20 primes twice (or throw "IllegalStateException: This sequence can be consumed only once.") but as anIteratorI would expect it to print the first 40 primes. These are conflicting expectations. I recommend only implementing one. ASequenceis understood to possibly be constrained to be iterated only once so I would implement the baseIteratorand then wrap it as aSequencein a top-level function for convenience.
e.g.
fun generatePrimeSequence() = PostponedPrimeIterator().asSequence()
class PostponedPrimeIterator() : AbstractIterator() {
private lateinit var basePrimes: PostponedPrimeIterator
private var basePrime = 0
private val sieve = mutableMapOf()
private val initialPrimes = sequenceOf(2, 3, 5, 7).iterator()
private var value = 0
override fun computeNext() {
if (initialPrimes.hasNext()) {
value = initialPrimes.next()
setNext(value)
} else {
value += 2
if (value == 9) {
basePrimes = PostponedPrimeIterator()
basePrimes.next()
basePrime = basePrimes.next()
assert(value == basePrime * basePrime)
}
while (value > 0) {
val factor = sieve.remove(value) ?:
if (value == basePrime * basePrime) {
basePrime.apply {
basePrime = basePrimes.next()
}
} else {
assert(value < basePrime * basePrime)
setNext(value)
break
}
var j = value + 2 * factor
while (j in sieve) {
j += 2 * factor
}
sieve[j] = factor
value += 2
}
}
}
}Usage:
generatePrimeSequence().take(20).forEach(::println)Code Snippets
fun generatePrimeSequence() = PostponedPrimeIterator().asSequence()
class PostponedPrimeIterator() : AbstractIterator<Int>() {
private lateinit var basePrimes: PostponedPrimeIterator
private var basePrime = 0
private val sieve = mutableMapOf<Int, Int>()
private val initialPrimes = sequenceOf(2, 3, 5, 7).iterator()
private var value = 0
override fun computeNext() {
if (initialPrimes.hasNext()) {
value = initialPrimes.next()
setNext(value)
} else {
value += 2
if (value == 9) {
basePrimes = PostponedPrimeIterator()
basePrimes.next()
basePrime = basePrimes.next()
assert(value == basePrime * basePrime)
}
while (value > 0) {
val factor = sieve.remove(value) ?:
if (value == basePrime * basePrime) {
basePrime.apply {
basePrime = basePrimes.next()
}
} else {
assert(value < basePrime * basePrime)
setNext(value)
break
}
var j = value + 2 * factor
while (j in sieve) {
j += 2 * factor
}
sieve[j] = factor
value += 2
}
}
}
}generatePrimeSequence().take(20).forEach(::println)Context
StackExchange Code Review Q#139635, answer score: 2
Revisions (0)
No revisions yet.