All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.util.ImmutableLRU.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.util

import scala.collection.SortedMap

/**
 * Immutable implementation of an LRU cache.
 */
object ImmutableLRU {

  /**
   * Build an immutable LRU key/value store that cannot grow larger than `maxSize`
   */
  def apply[K,V](maxSize: Int): ImmutableLRU[K,V] = {
    new ImmutableLRU(maxSize, 0, Map.empty[K,(Long,V)], SortedMap.empty[Long,K])
  }
}

/**
 * An immutable key/value store that evicts the least recently accessed elements
 * to stay constrained in a maximum size bound.
 */
// "map" is the backing store used to hold key->(index,value)
// pairs. The index tracks the access time for a particular key. "ord"
// is used to determine the Least-Recently-Used key in "map" by taking
// the minimum index.
class ImmutableLRU[K,V] private (maxSize: Int, idx: Long, map: Map[K,(Long,V)], ord: SortedMap[Long,K]) {

  // Scala's SortedMap requires a key ordering; ImmutableLRU doesn't
  // care about pulling a minimum value out of the SortedMap, so the
  // following kOrd treats every value as equal.
  protected implicit val kOrd = new Ordering[K] { def compare(l:K,r:K) = 0 }

  /**
   * the number of entries in the cache
   */
  def size: Int = map.size

  /**
   * the `Set` of all keys in the LRU
   * @note accessing this set does not update the element LRU ordering
   */
  def keySet: Set[K] = map.keySet

  /**
   * Build a new LRU containing the given key/value
   * @return a tuple with of the following two items:
   *          _1 represents the evicted entry (if the given lru is at the maximum size)
   *             or None if the lru is not at capacity yet.
   *          _2 is the new lru with the given key/value pair inserted.
   */
  def +(kv: (K,V)): (Option[K], ImmutableLRU[K,V]) = {
    val (key, value) = kv
    val newIdx = idx + 1
    val newMap = map + (key -> ((newIdx, value)))
    // Now update the ordered cache:
    val baseOrd = map.get(key).map { case (id, _) => ord - id }.getOrElse(ord)
    val ordWithNewKey = baseOrd + (newIdx -> key)
    // Do we need to remove an old key:
    val (evicts, finalMap, finalOrd) = if(ordWithNewKey.size > maxSize) {
      val (minIdx, eKey) = ordWithNewKey.min
      (Some(eKey), newMap - eKey, ordWithNewKey - minIdx)
    }
    else {
      (None, newMap, ordWithNewKey)
    }
    (evicts, new ImmutableLRU[K,V](maxSize, newIdx, finalMap, finalOrd))
  }

  /**
   * If the key is present in the cache, returns the pair of
   * Some(value) and the cache with the key's entry as the most recently accessed.
   * Else, returns None and the unmodified cache.
   */
  def get(k: K): (Option[V], ImmutableLRU[K,V]) = {
    val (optionalValue, lru) = remove(k)
    val newLru = optionalValue.map { v => (lru + (k -> v))._2 } getOrElse(lru)
    (optionalValue, newLru)
  }

  /**
   * If the key is present in the cache, returns the pair of
   * Some(value) and the cache with the key removed.
   * Else, returns None and the unmodified cache.
   */
  def remove(k: K): (Option[V], ImmutableLRU[K,V]) =
    map.get(k).map { case (kidx, v) =>
      val newMap = map - k
      val newOrd = ord - kidx
      // Note we don't increase the idx on a remove, only on put:
      (Some(v), new ImmutableLRU[K,V](maxSize, idx, newMap, newOrd))
    }.getOrElse( (None, this) )

  override def toString = { "ImmutableLRU(" + map.toList.mkString(",") + ")" }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy