Home > Blockchain >  How to implement a map of lazy values in Kotlin
How to implement a map of lazy values in Kotlin

Time:11-19

I have a bunch of lookup tables indexed by key that I would like instantiate lazily (i.e. the tables are expensive to compute and I only expect some of them to be used on any given execution of the code).

private var myLazyMap: Map<KeyClass, TableClass> by lazy { ...}

Doesn't work as that makes the map object itself lazy, which isn't right. I think I may need to write a custom delegate, but I still can't see how to embed that into the map object.

I could wrap TableClass with something like

class LazyTable(val param: TableClassParameter) {
   private var table: TableClass by lazy { TableClass(param) }

   fun wrappedTableFun(): ResultClass {
     return table.tableFun()
   }
}

But this does mean the class is wrong and it feels like a hack. Can this be done in a neater way?

CodePudding user response:

It could be implemented in multiple ways, depending on your needs. Probably the easiest is to use a map of lazy values directly:

val map = mutableMapOf<KeyClass, Lazy<TableClass>>()

map[myKey1] = lazy { createTable1() }
map[myKey2] = lazy { createTable2() }

val table = map[myKey1]?.value

If we want to not expose Lazy to the users of the map, we need to create our own LazyMap. One way is to use a map similar to above and just hide Lazy from the user:

class LazyMap<K, V>(
    private val map: Map<K, Lazy<V>>
) {
    operator fun get(key: K): V? = map[key]?.value
}

Another solution is to use a function that creates values when needed:

class LazyMap<K, V>(
    private val compute: (K) -> V
) {
    private val map = mutableMapOf<K, V>()

    operator fun get(key: K): V? = map.getOrPut(key) { compute(key) }
}

We can also use a separate compute function per each key, as in the answer by @tibtof .

CodePudding user response:

A partial implementation could be something like this:

class LazyMap<K, V>(val lazyVals: Map<K, () -> V>, val cache: MutableMap<K, V> = mutableMapOf()) : Map<K, V> by cache {

    companion object {
        fun <K, V> lazyMapOf(vararg entries: Pair<K, () -> V>): LazyMap<K, V> = LazyMap(mapOf(*entries))
    }

    override fun get(key: K): V? = cache[key] ?: lazyVals[key]?.let { cache[key] = it(); cache[key] }
}

fun main() {
    val lazyMap = lazyMapOf(
        1 to { println("computing one"); "one" },
        2 to { println("computing two"); "two" }
    )

    println("get 2")
    println(lazyMap[2])
    println("get 1")
    println(lazyMap[1])
    println("get 0")
    println(lazyMap[0])
    println("get 2 again")
    println(lazyMap[2])
}

An we can observe the lazyness in the output:

get 2
computing two
two
get 1
computing one
one
get 0
null
get 2 again
two
  • Related