Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EvictingQueue in Kotlin

I need a collection that:

  • has a fixed size
  • is mutable
  • will re-index elements once a new element is added, removing the oldest element (like a shift register)

I think Guava's EvictingQueue and Apache Commons CircularFifoQueue is what I need.

However, I am on Android, and was hoping Kotlin already has something similar. But I did not find yet ;)

like image 931
pratclot Avatar asked Oct 19 '25 15:10

pratclot


1 Answers

Kotlin has ArrayDeque, which has simple methods for removing and adding elements to a list.

It doesn't have a limited capacity though.

Using a custom Delegated property is a fairly lightweight way to wrap an existing ArrayDeque so we can intercept calls and ensure the limit is maintained.

dequeLimiter

The method dequeLimiter is based on the NotNullVar delegate.

Whenever the internal deque field is read or written, it calls applyLimit() and this removes any excess items.

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty


fun <E> dequeLimiter(limit: Int): ReadWriteProperty<Any?, ArrayDeque<E>> =
  object : ReadWriteProperty<Any?, ArrayDeque<E>> {

    private var deque: ArrayDeque<E> = ArrayDeque(limit)

    private fun applyLimit() {
      while (deque.size > limit) {
        val removed = deque.removeFirst()
        println("dequeLimiter removed $removed")
      }
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): ArrayDeque<E> {
      applyLimit()
      return deque
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: ArrayDeque<E>) {
      this.deque = value
      applyLimit()
    }
  }

Usage

The delegate is applied on any ArrayDeque with the by keyword (see the docs for more info).

After this, the deque can be used as normal and will be automatically limited.

fun main() {

  // create a deque, and use our property delegate to intercept
  // invocations and limit the size to 3
  val limitedDeque: ArrayDeque<Int> by dequeLimiter(3)

  // try adding 5 elements
  (0..5).forEach {
    limitedDeque.add(it)
    println("limitedDeque: $limitedDeque")
  }
  /* output */
  // limitedDeque: [0]
  // limitedDeque: [0, 1]
  // limitedDeque: [0, 1, 2]
  // dequeLimiter removed 0
  // limitedDeque: [1, 2, 3]
  // dequeLimiter removed 1
  // limitedDeque: [2, 3, 4]
  // dequeLimiter removed 2
  // limitedDeque: [3, 4, 5]

  // try removing 5 elements
  repeat(5) {
    val removedLast = limitedDeque.removeLastOrNull()
    println("removedLast: $removedLast")
    println("limitedDeque: $limitedDeque")
  }
  /* output */
  // removedLast: 5
  // limitedDeque: [3, 4]
  // removedLast: 4
  // limitedDeque: [3]
  // removedLast: 3
  // limitedDeque: []
  // removedLast: null
  // limitedDeque: []
  // removedLast: null
  // limitedDeque: []

}
like image 116
aSemy Avatar answered Oct 21 '25 03:10

aSemy