Create EnumMap from List of values in Kotlin

J.E.Tkaczyk picture J.E.Tkaczyk · Jun 21, 2018 · Viewed 9k times · Source

I would like to define an extension of the enum class which accepts a list of values in the same order as the enum constants and outputs an EnumMap.

What I've been able to do instead is create and extension of the List<V> object with an array of the keys from Enum<K>.values() as input.

It seems the difficulty is enum class is not itself a object having a .values() method. So perhaps my request is not sensible.

Here is a working example using the list extension method.

import java.util.*

enum class Shoes(
    val title: String,
    val weight: Double) {
    WORKBOOT("tough", 11.0),
    SNEAKER("fast", 6.0),
    SLIPPER("soft", 3.0);
}

fun main(args: Array<String>) {
    val prices= listOf(11.5,8.2,3.5)
    val map = prices.enumMapOf(Shoes.values())
    map.print()
}

inline fun <reified K : Enum<K>, V> List<V>.enumMapOf(keys:Array<K>): EnumMap<K, V> {
    val map = EnumMap<K, V>(K::class.java)
    keys.forEachIndexed{i,k-> map[k]=this[i]}
    return map
}

fun <K:Enum<K>,V> EnumMap<K,V>.print() {
    this.forEach{k,v-> println("%d: %s --> %.2f".format(k.ordinal,k.name,v)) }
}

Answer

zsmb13 picture zsmb13 · Jun 21, 2018

I think the main thing you're missing is the enumValues method that can give you the values of a generic enum.

This would allow you to do this (I've rearranged the code of creating the EnumMap as well, you can stick to your own solution if this seems too complicated of course):

inline fun <reified K : Enum<K>, V> Enum<K>.mappedTo(values: List<V>): EnumMap<K, V> {
    return enumValues<K>().zip(values).toMap(EnumMap(K::class.java))
}

Of course this issue now is the call syntax - this function would have to be called like this, on an instance of an enum:

Shoes.WORKBOOT.mappedTo(listOf(11.5, 8.2, 3.5))

Which doesn't make a lot of sense. It would be nicer to make this call:

Shoes.mappedTo(listOf(11.5, 8.2, 3.5))

But as far as I know, there is no way to achieve this syntax, because you can't add an extension to the companion object of the generic enum and then expect to call that on a specific enum's companion object - those don't inherit from each other like that.

So perhaps instead of creating an extension, you could use a regular function, and get the enum's type from the generic parameter instead of the receiver:

inline fun <reified K : Enum<K>, V> mapEnumTo(values: List<V>): EnumMap<K, V> {
    return enumValues<K>().zip(values).toMap(EnumMap(K::class.java))
}

mapEnumTo<Shoes, Double>(listOf(11.5, 8.2, 3.5))

You do have to specify the type of the key here as well, because you can't have a generic type parameter that's explicit and then another that's implicit. But I think this is pretty nice syntax, as you see the key and value types of the map you're creating.