Skip to content

Instantly share code, notes, and snippets.

@gkeramidas
Created September 24, 2013 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gkeramidas/6683198 to your computer and use it in GitHub Desktop.
Save gkeramidas/6683198 to your computer and use it in GitHub Desktop.
Caching the results of primality check, and expiring after a tunable TTL
import java.util.concurrent.atomic.AtomicReference
import collection.immutable.HashMap
/*
* A module which implements a primality cache for integers, with a max-age parameter which controls
* when a primality result will be considered 'expired' and recalculated from scratch.
*/
trait PrimeCacheModule {
/** The current time in milliseconds. Timestamp for cache entries and 'now' */
def now = System.currentTimeMillis
class PrimeCache(maxAge: Long) {
case class CacheEntry(number: Long, prime: Boolean, time: Long) {
def age: Long = (now - time)
}
/** Returns the current cache, excluding entries which have expired. */
def primes: HashMap[Long, CacheEntry] =
cache.get.filter{ case (key, entry) => entry.age <= maxAge }
/** Atomically updated reference to a HashMap holding the current cache. */
private lazy val cache: AtomicReference[HashMap[Long, CacheEntry]] =
new AtomicReference(HashMap[Long, CacheEntry]())
/** Check if a number is prime, using the primes cache for speedups. */
def check(number: Long): Boolean = {
primes.find{ case (key, entry) => key == number && entry.age <= maxAge } match {
case Some((key, entry)) =>
println("Cache hit for %8s => %-50s age: %s".format(key, entry, entry.age))
entry.prime // Cache hit, but no update; aging continues normally.
case _ =>
println("Cache miss for %s => calculating".format(number))
val entry = CacheEntry(number, isPrime(number), now)
cache.set(primes + (number -> entry))
entry.prime
}
}
/** Silly and expensive primality check. */
def isPrime(number: Long): Boolean = {
def isPrimeIter(number: Long, divisor: Long): Boolean =
divisor match {
case d if (d > number / 2) => true
case d if (number % d == 0) => false
case _ =>
isPrimeIter(number, divisor + 1)
}
number match {
case n if (n < 1) => false
case _ => isPrimeIter(number, 2)
}
}
}
}
class Primes(ttl: Long) extends PrimeCacheModule {
val cache = new PrimeCache(maxAge = ttl)
/** Checks is a number is prime, consulting the cache for any known prime values. */
def check(number: Long): Boolean = cache.check(number)
}
//
// Sample code using Primes(ttl: Long)
//
val foo = new Primes(15000) // ttl of cache entries = 15 seconds
(1 to 40).foreach{k => foo.check(k.toLong)} // should print 'Cache miss' for all entries
(1 to 10).foreach{k => foo.check(k.toLong)} // should print 'Cache hit' for all entries
(1 to 10).foreach{k => foo.check(k.toLong)} // should print 'Cache hit' for all entries
/** Should display the cached entries which have not already expired. */
foo.cache.primes.foreach{case (k, v) => println("entry %-50s age: %d".format(v, v.age))}
@gkeramidas
Copy link
Author

scala> val foo = new Primes(15000)
foo: Primes = Primes@5fe637fb

scala> (1 to 40).foreach{k => foo.check(k.toLong)}
Cache miss for 1 => calculating
Cache miss for 2 => calculating
Cache miss for 3 => calculating
Cache miss for 4 => calculating
Cache miss for 5 => calculating
Cache miss for 6 => calculating
Cache miss for 7 => calculating
Cache miss for 8 => calculating
Cache miss for 9 => calculating
Cache miss for 10 => calculating
Cache miss for 11 => calculating
Cache miss for 12 => calculating
Cache miss for 13 => calculating
Cache miss for 14 => calculating
Cache miss for 15 => calculating
Cache miss for 16 => calculating
Cache miss for 17 => calculating
Cache miss for 18 => calculating
Cache miss for 19 => calculating
Cache miss for 20 => calculating
Cache miss for 21 => calculating
Cache miss for 22 => calculating
Cache miss for 23 => calculating
Cache miss for 24 => calculating
Cache miss for 25 => calculating
Cache miss for 26 => calculating
Cache miss for 27 => calculating
Cache miss for 28 => calculating
Cache miss for 29 => calculating
Cache miss for 30 => calculating
Cache miss for 31 => calculating
Cache miss for 32 => calculating
Cache miss for 33 => calculating
Cache miss for 34 => calculating
Cache miss for 35 => calculating
Cache miss for 36 => calculating
Cache miss for 37 => calculating
Cache miss for 38 => calculating
Cache miss for 39 => calculating
Cache miss for 40 => calculating

scala> (1 to 10).foreach{k => foo.check(k.toLong)}
Cache hit  for        1 => CacheEntry(1,true,1380020564757)                   age: 5274
Cache hit  for        2 => CacheEntry(2,true,1380020564757)                   age: 5274
Cache hit  for        3 => CacheEntry(3,true,1380020564757)                   age: 5275
Cache hit  for        4 => CacheEntry(4,false,1380020564757)                  age: 5275
Cache hit  for        5 => CacheEntry(5,true,1380020564758)                   age: 5275
Cache hit  for        6 => CacheEntry(6,false,1380020564758)                  age: 5275
Cache hit  for        7 => CacheEntry(7,true,1380020564758)                   age: 5275
Cache hit  for        8 => CacheEntry(8,false,1380020564758)                  age: 5276
Cache hit  for        9 => CacheEntry(9,false,1380020564758)                  age: 5276
Cache hit  for       10 => CacheEntry(10,false,1380020564758)                 age: 5276

scala> (1 to 10).foreach{k => foo.check(k.toLong)}
Cache hit  for        1 => CacheEntry(1,true,1380020564757)                   age: 14866
Cache hit  for        2 => CacheEntry(2,true,1380020564757)                   age: 14866
Cache hit  for        3 => CacheEntry(3,true,1380020564757)                   age: 14867
Cache hit  for        4 => CacheEntry(4,false,1380020564757)                  age: 14867
Cache hit  for        5 => CacheEntry(5,true,1380020564758)                   age: 14866
Cache hit  for        6 => CacheEntry(6,false,1380020564758)                  age: 14867
Cache hit  for        7 => CacheEntry(7,true,1380020564758)                   age: 14867
Cache hit  for        8 => CacheEntry(8,false,1380020564758)                  age: 14867
Cache hit  for        9 => CacheEntry(9,false,1380020564758)                  age: 14868
Cache hit  for       10 => CacheEntry(10,false,1380020564758)                 age: 14868

// 14800+ milliseconds is close to maxAge, cached entries about to expire.

scala> foo.cache.primes.foreach{case (k, v) => println("entry %-50s age: %d".format(v, v.age))}

// no result; all entries have expired

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment