Home > front end >  Kotlin toSortedMap() with custom Comparator not working properly
Kotlin toSortedMap() with custom Comparator not working properly

Time:01-14

I need to group and sort an API result by 1st character of name. All names starting with a letter should come in order, followed by names starting with non-alphabet grouped into "#" and placed at end. Below is a simplified code:

data class Model(val title: String)

fun main() {
    val mList = arrayListOf(Model("pqr"), Model("abc"), Model("3AM"), Model("%s5"))

    val mComparator = Comparator<String> { o1, o2 ->
        when {
            o1 == "#" -> 1
            o2 == "#" -> -1
            else -> o1.compareTo(o2) 
        }
    }

    val mMap = mList.groupBy {
        if (it.title[0].isLetter()) it.title[0].toString()
        else "#"
    }.toSortedMap(mComparator)
    
    val outList = arrayListOf<List<Model>?>()
    mMap.keys.forEach {
        outList.add(mMap[it])
    }
    
    println(mMap)
    println(mMap.keys)
    println(mMap.values)
    println(mMap["#"])
    println(outList)
}

Here's output of above code:

{a=[Model(title=abc)], p=[Model(title=pqr)], #=[Model(title=3AM), Model(title=%s5)]}
[a, p, #]
[[Model(title=abc)], [Model(title=pqr)], [Model(title=3AM), Model(title=%s5)]]
null
[[Model(title=abc)], [Model(title=pqr)], null]

The problem is that mMap["#"] gives null although mMap, mMap.keys, mMap.values show "#" in keys and also the corresponding values.

Don't understand why mMap["#"] returns null rather than [Model(title=3AM), Model(title=%s5)]

CodePudding user response:

Your comparator breaks the contract that a Comparator must satisfy. It must be reflexive, meaning it gives the exact opposite result if you swap the order of the two inputs. A corrected version of your comparator:

val mComparator = Comparator<String> { o1, o2 ->
    when {
        o1 == "#" && o2 != "#" -> 1
        o2 == "#" && o1 != "#" -> -1
        else -> o1.compareTo(o2)
    }
}

TreeMap depends on a correct Comparator for its functionality. Apparently, its comparator is even involved when using treeMap.get such that in your case it returns the nonsensical result of null.

By the way, putting an "m" prefix before a variable name is Hungarian notation that means the prefix is a member variable. But these are not member variables. They are local variables. Kotlin doesn't even have member variables, but properties are analogous. So, you should not be using "m" prefixes here because it communicates incorrectly about the meaning of your code. Anyway, most developers avoid using Hungarian notation because it makes code harder to read. A lot of official Android examples use it because of the Android development team's internal code style, but that is not a recommendation that you use it in your own code.

CodePudding user response:

val mComparator = Comparator<String> { o1, o2 ->
  when {
    (o1 == "#").xor(o2 == "#") -> if (o1 == "#") 1 else -1
    else                       -> o1.compareTo(o2)
  }
}
  •  Tags:  
  • Related