Avoid duplicate keys in map literals

Hi,

I am declaring a map literal in my code. This eventually will be a long map and has a chance of keys being repeated that I want to avoid. Also, I don’t want to sort declaration based on keys to keep them grouped based on what source they were picked from and easy to add to the list.

I am using IntelliJ Idea and I’m ok if I can set some inspection preference to highlight any duplicate keys. I tried looking up but didn’t find any. I also tried using detekt plugin to highlight the issue for me, but couldn’t get it working either.

Following is a small example of a map with duplicate keys that Kotlin allows, but I want to be highlighted in the editor so that I can avoid it:

val MF_RR_HeaderMap = mapOf<String, MFRRHeader>(
    // Source 1
    "unit" to MFRRHeader.UNIT_NUMBER,
    "unit type" to MFRRHeader.UNIT_TYPE,
    "resident code" to MFRRHeader.TENANT_CODE,
    "resident name" to MFRRHeader.TENANT_NAME,
    // first entry
    "market rent" to MFRRHeader.MARKET_RENT,

    // Source 2
    "type" to MFRRHeader.UNIT_TYPE,
    "sq. feet" to MFRRHeader.SQFT,
    "residents" to MFRRHeader.TENANT_NAME,
    "status" to MFRRHeader.STATUS,

    // This is the duplicate entry that I want to be highlighted
    "market rent" to MFRRHeader.STATUS,
)
2 Likes

You could write a little DSL for this, like:

inline fun <K, V> dupFreeMap(block: DupFreeBlock<K, V>.() -> Unit): Map<K, V> =
    DupFreeBlock<K, V>().apply(block).pairs.toMap()

class DupFreeBlock<K, V> {
    val pairs = mutableListOf<Pair<K, V>>()

    operator fun K.rangeTo(v: V) {
        check(pairs.none { (k,_) -> k == this }){ "Duplicate key '$this'" }
        pairs += this to v
    }
}

fun main() {

    val MF_RR_HeaderMap = dupFreeMap<String, MFRRHeader> {
        // Source 1
        "unit" .. MFRRHeader.UNIT_NUMBER
        "unit type" .. MFRRHeader.UNIT_TYPE
        "resident code" .. MFRRHeader.TENANT_CODE
        "resident name" .. MFRRHeader.TENANT_NAME
        // first entry
        "market rent" .. MFRRHeader.MARKET_RENT

        // Source 2
        "type" .. MFRRHeader.UNIT_TYPE
        "sq. feet" .. MFRRHeader.SQFT
        "residents" .. MFRRHeader.TENANT_NAME
        "status" .. MFRRHeader.STATUS

        // This is the duplicate entry that I want to be highlighted
        "market rent" .. MFRRHeader.STATUS
    }

    println(MF_RR_HeaderMap)
}

Which would result in Exception in thread "main" java.lang.IllegalStateException: Duplicate key 'market rent'

1 Like

Instead of a DSL, you could easily make a mapOfWithoutDuplicates function that does the same thing

Another possibility:

val MF_RR_HeaderMap = buildMap<String, MFRRHeader> {
    infix fun String.to(value: MFRRHeader) =
        require(put(this, value) == null) { "Duplicate key $this" }

    // Source 1
    "unit" to MFRRHeader.UNIT_NUMBER
    "unit type" to MFRRHeader.UNIT_TYPE
    "resident code" to MFRRHeader.TENANT_CODE
    "resident name" to MFRRHeader.TENANT_NAME
    // first entry
    "market rent" to MFRRHeader.MARKET_RENT

    // Source 2
    "type" to MFRRHeader.UNIT_TYPE
    "sq. feet" to MFRRHeader.SQFT
    "residents" to MFRRHeader.TENANT_NAME
    "status" to MFRRHeader.STATUS

    // This is the duplicate entry that I want to be highlighted
    "market rent" to MFRRHeader.STATUS
}
1 Like

True.

fun <K, V> dupFreeMap(vararg args: Pair<K, V>): Map<K, V> {
    val dupKeys = args
        .groupingBy { it.first }
        .eachCount()
        .filter { it.value > 1 }
        .keys
    check(dupKeys.isEmpty()) {
        "Duplicate keys '$dupKeys'"
    }
    return args.toMap()
}
1 Like

Even simpler:

fun <K, V> dupFreeMap(vararg pairs: Pair<K, V>): Map<K, V> = mapOf(pairs = pairs).also { require(it.keys.size == pairs.size) }
1 Like

Also true, but it’s cooler to spit out the duplicate keys.

1 Like

It is indeed cooler to spit out the duplicate keys but duplicates are presumably rare (possibly a programming error), maybe occurring less than 1% of the time, and therefore the duplicate check shouldn’t use much runtime (e.g. by creating a Grouping).