Why toSortedMap changing size of the original map?

Let’s review simple example:

val map = mapOf<String, Int>("test1" to 1, "test2" to 2)

    val sorted = map.toSortedMap { a, b ->
        map[b]?.compareTo(map[a] ?: 0) ?: 0
    }
    println(sorted)

The output will be {test2=2, test1=1}

Then if we change to

val map = mapOf<String, Int>("test1" to 1, "test2" to 1)

    val sorted = map.toSortedMap { a, b ->
        map[b]?.compareTo(map[a] ?: 0) ?: 0
    }
    println(sorted)

Then the size of sorted map suddenly shrink to 1 instead of 2 and show output:
{test1=1}

in an other hand if I sort map like this:
val sorted = map.toSortedMap()
it will keep sorted map size to original 2 elements with output:
{test1=1, test2=1}

Can anyone explain why?

Take a closer look into the TreeMap implementation. This map doesn’t use hashCode() & equals() to verify equality of the elements, but the result of comparison itself. The comparison value of 0 means that both elements are equal in this situation, therefore ‘duplicates’ are simply cut off.

As for more practical advice how to fix it, it will depend on what you what to achieve from API perspective.

1 Like

My guess is that you wanted a map-like structure where you access elements via String keys with constant time, but when iterating you want an order going by values? You can for example implement map like this:

class MapSortedByValues<K, V : Comparable<V>>(sourceMap: Map<K, V>) : Map<K, V> {
	private val orderedEntries = sourceMap.entries.sortedBy(Map.Entry<K, V>::value)
	private val indexMap = orderedEntries.withIndex().associate { (index, entry) -> entry.key to index }
	private val orderedValues = orderedEntries.map(Map.Entry<K, V>::value)
	
	override val entries get() = object : Set<Map.Entry<K, V>> {
		override val size get() = orderedEntries.size
		
		override fun contains(element: Map.Entry<K, V>) = element in orderedEntries
		
		override fun containsAll(elements: Collection<Map.Entry<K, V>>) = orderedEntries.containsAll(elements)
		
		override fun isEmpty() = orderedEntries.isEmpty()
		
		override fun iterator() = orderedEntries.iterator()
	}
	
	override val keys get() = indexMap.keys
	
	override val size get() = indexMap.size
	
	override val values get() = orderedValues
	
	override fun containsKey(key: K) = key in indexMap
	
	override fun containsValue(value: V) = value in orderedValues
	
	override fun get(key: K) = indexMap[key]?.let(orderedValues::get)
	
	override fun isEmpty() = indexMap.isEmpty()
	
	override fun hashCode() = orderedEntries.hashCode()
	
	override fun equals(other: Any?) = this === other || other is MapSortedByValues<*, *> && orderedEntries == other.orderedEntries
	
	override fun toString() = orderedEntries.joinToString(separator = ", ", prefix = "{", postfix = "}") { "${it.key}=${it.value}" }
}

fun main() {
	val map = mapOf("test1" to 2, "test2" to 1)
	println(map)
	val sorted = MapSortedByValues(map)
	println(sorted)
}