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
- 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<Int>(), Sequence<Int> {
override fun iterator() = this
private var basePrimes: PostponedPrimeSequence? = null
private var basePrime = 0
private var sieve = mutableMapOf<Int, Int>()
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 < basePrime * basePrime)
setNext(candidate)
break
}
var j = candidate + 2 * factor
while (sieve.containsKey(j)) {
j += 2 * factor
}
sieve[j] = factor
candidate += 2
}
candidate += 2
}
}
}
Usage:
PostponedPrimeSequence().take(20).forEach(::println)
All feedback welcome, though reviews are encouraged to look at:
- Naming: As I'm new to Kotlin, I don't know the naming conventions yet
- Performance: If there are simple, provable improvements without drastically changing the algorithm (such as adding a large wheel)
- Iteration organization: As Kotlin lacks a true
yield
ing generator and I wanted a trueIterator
/Sequence
, rather than a callback based "poor man'syield
", the iteration isn't exactly the prettiest matter. Thecandidate += 2 } candidate += 2
bit especially feels like there should be a better way to do this, and thebreak
(which is related) breaks ha simple control flow, which is not ideal. !!
: Even on the line immediately following assignment tobasePrimes
, I have to use!!
to potentially fail on accessing thenext
method (Smart cast to 'PostponedPrimeSequence' is impossible, because 'basePrimes' is a mutable property that could have been changed by this time
). I know that, due to reflection, any variable on the JVM could change at any time, but this seemslike a smelly smell that smellssmelly.basePrimes
has to be mutable and start outnull
, though, because otherwise the class would recursively take infinite space. In this case, though, I want to initialize it once, late, and from then on it should act as an immutable (non-nullable)val
reference. If this is possible in Kotlin, I don't know how to do so yet.
lang-kotlin
. \$\endgroup\$default
when usinglang-kotlin
. It seems to do just as well, though, so I'll switch. (The biggest hint isfun
is not highlighted as a keyword.) \$\endgroup\$lang-kotlin
just the same. \$\endgroup\$log(log N) = log( (2**k)*log x) = k*log 2 + log(log x)
so the height of that "tower" of "nested primes generators" is k = O(log (log N)). IOW that recursion that "terminates eventually" actually does so in O(log log N) steps. An interesting tidbit. \$\endgroup\$