admin管理员组

文章数量:1400174

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,
)

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,
)
Share Improve this question asked Mar 25 at 11:22 chetan vermachetan verma 436 bronze badges 1
  • Thanks @kent and @andrewl! I am going with Kent's answer as I can reuse the same function for other maps declarations as well. – chetan verma Commented Mar 25 at 12:03
Add a comment  | 

3 Answers 3

Reset to default 3

You can write a function wrapping the standard mapOf, and throw IAE when duplicate keys hit:

public fun <K, V> myMapOf(vararg pairs: Pair<K, V>): Map<K, V> {
    val dupKeys = pairs.groupingBy { it.first }.eachCount().filter { it.value > 1 }
    require(dupKeys.isEmpty()) { "duplicate keys: ${dupKeys.keys.joinToString(separator = ", ")}" }
    return mapOf(*pairs)
}

val oops = myMapOf(
    "a" to "b",
    "b" to "c",
    "c" to "d",
    "a" to "e",
    "b" to "f",
)

You can also play it on the playground

Two approaches

  1. Write yourself some kind of annotation processor that runs at compile time (I cannot help you with that)
  2. Write it a bit differently and have a unit test to verify:
    val MF_RR_HeaderMap = listOf<Pair<String, MFRRHeader>>(
        // Source 1
        "unit" to MFRRHeader.UNIT_NUMBER,
        ...
    ).let {
        val map = mutableMapOf<String, MFRRHeader>()
        it.forEach { (key, value) ->
            require(map.put(key, value) == null) { "Duplication of key $key" }
        }
        map
    }

Then have a Unit test that simply instantiates the class, and if duplicates exist you will get an error listing the duplicate key

My answer minimizes the runtime overhead of checking for duplicates in map literals. This is desirable because the discovery of duplicates happens at most once per source-code change (ideally it should happen at compile time, but that's difficult to implement) whereas the building of the map happens every time the application is run: potentially thousands of times. You don't want to add runtime overhead every single time the application is run, just because you want to perform a check that is relevant only once in the application's development.

public fun <K, V> noDuplicatesMapOf(vararg pairs: Pair<K, V>) = mapOf(*pairs)
    .also {
        require(it.size == pairs.size) {
            val dups = pairs.groupingBy { it.first }.eachCount().filter { it.value > 1 }
            "Map literal has duplicate keys ${dups.keys}"
        }
}

val ok = noDuplicatesMapOf(
    "a" to 0,
    "b" to 1
)

val willThrowIllegalArgumentException = noDuplicatesMapOf(
    "a" to 0,
    "b" to 1,
    "a" to 2,
    "b" to 3
)

The check it.size == pairs.size is so fast that the runtime overhead will be unnoticeable compared to just using mapOf.

本文标签: Avoid having duplicate keys in a map literal in KotlinStack Overflow