Kotlin sortedMap compared by the Pair value not the key - kotlin

I found this statement
sortedMapOf(compareBy<String> { it.length }, Pair("abc", 1), Pair("c", 3), Pair("bd", 4), Pair("bc", 2))
So it sorts first by the length of the key. However what I need is a sort by the value of the map's pairs.
Therefore I tried this:
mapOf("eat" to 11.0, "sleep" to 50.0).toSortedMap(Comparator<Pair<String, Double>> { o1, o2 -> o1.second.compareTo(o2.second) })
For some reason the part Comparator<Pair<String, Double>> { o1, o2 -> o1.second.compareTo(o2.second) } gives me an error.
My IDE (IntelliJ) tells me this:
Type mismatch.
Required: Pair<String, Double>
Found: String
I don't get it where am I passing a String. All I can see is me using the Pair.

Key in mapOf("eat" to 11.0, "sleep" to 50.0) is of type String, hence toSortedMap is expecting you to provide a comparator that takes string as argument.
To make the code runnable, you'll have to make a new map where the key is of type Pair<String, Double>.
mapOf("eat" to 11.0, "sleep" to 50.0)
.map { Pair(it.key, it.value) to it.value }
.toMap()
.toSortedMap { o1, o2 -> o1.second.compareTo(o2.second) }
This is of course strange code, but it should answer the question.

SortedMaps are sorted on their keys - you can't sort them on their values. And here's the signature of sortedMapOf():
fun <K, V> sortedMapOf(
comparator: Comparator<in K>,
vararg pairs: Pair<K, V>
): SortedMap<K, V>
The comparator you're passing in explicitly takes the key's type, i.e. the first value of each Pair is getting passed to the comparator, not the whole Pair.
That's why you're getting the error about Strings and Pairs - you told the Comparator that it's getting keys that are Pair<String, Double>s, but the Map you're passing in has String keys and Double values (since a Pair is treated as a key/value item).
You can't do what you're trying to do, basically! Is there another way to achieve whatever you're doing? A typical approach would be to just use a normal map, and make a sorted list when you need the output:
val myMap = mapOf("eat" to 11.0, "sleep" to 50.0)
val sorted = myMap.entries.sortedBy {it.value}

Related

Why does mapOf() with multiple types of keys return a Map with an `out` key parameter?

In Kotlin 1.7.0,
mapOf("s" to "s2", 0 to 5)
gives this response in the ki REPL:
res1: Map<out Any, Any> = {s=s2, 0=5}
My question is, why is the key parameter out (covariant)? Normally I might not care, but I'm trying to understand Kotlin generics, and specifically how variance works.
According to the docs, the signature for mapOf() is
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>
I can understand that in order to cover both String and Int types, the K type parameter has to be Any. But why out? And why out for K and not for V?
If we try a mutable map, we get out for both:
mutableMapOf("s" to "s2", 0 to 5)
gives
MutableMap<out Any, out Any>
This makes even less sense to me. At least K is consistent with V in both being out. But now we can't add or change any of the mappings in our mutable map, because keys and values are both of a "producer" type:
val m2 = mutableMapOf("s" to "s2", 0 to 5)
[10] m2.put(3 to 6)
ERROR Type mismatch: inferred type is Pair<Int, Int> but Nothing was expected (Line_11.kts:1:8)
ERROR No value passed for parameter 'value' (Line_11.kts:1:14)
[15] m2[3] = "fi"
ERROR The integer literal does not conform to the expected type CapturedType(out Any) (Line_16.kts:1:4)
ERROR Type mismatch: inferred type is String but CapturedType(out Any) was expected (Line_16.kts:1:9)
We can fixed this by explicitly specifying the type parameters of our mutable map:
val m3: MutableMap<Any, Any> = mutableMapOf(3 to 4, "a" to "b")
But I'm wondering why these parameters in the inferred type are out. Is this intentional, or is it an unfortunate side effect of the generics system?
Update:
When I try something similar in the IDE, the out annotation is apparently not generated:
val myMap = mutableMapOf("s" to "s2", 0 to 5)
myMap[3] = 7
The IDE says the type of myMap is MutableMap<{Comparable*> & java.io.Serializable}, {Comparable*> & java.io.Serializable}>, as Tenfour04 also said in the comments. The ability to assign myMap[3] = 7 shows that the key is not annotated out. So I'm guessing that the out comes from something unique to the ki shell, or from Kotlin 1.7.0 (whereas the IDE seems to be running Kotlin 1.7.20).
Update 2:
I've now run this in a Kotlin scratch file in Android Studio (under Kotlin 1.7.20, on JVM). The following are local variables in main():
val m = mapOf("s" to "s2", 0 to 5)
printType(m)
val mm = mutableMapOf("s" to "s2", 0 to 5)
mm[3] = 7
printType(mm)
I'm using the printType() function that #broot linked to in comments:
import kotlin.reflect.typeOf
inline fun <reified T> printType(obj: T) {
println(typeOf<T>())
}
The results:
kotlin.collections.Map<out kotlin.Any, kotlin.Any>
kotlin.collections.MutableMap<out kotlin.Any, out kotlin.Any>
These are the same results I described at the beginning of the question.
This seems to me to show that the out annotation is not coming from something specific to the ki shell, nor from Kotlin 1.7.0.

How can I create a Map with non-null values from a set of nullable items in Kotlin?

In the Kotlin code, I want to add two fields to the map like this:
val myMap: Map<Type, List<Parameter>> = mapOf(
Pair(Type.POST, request.postDetails.postParameters),//postDetails can be nullable
Pair(Type.COMMENT, request.commentDetails.commentParameters)//commentDetails can be nullable
)
The above code is showing error at postDetails.postParameters and commentDetails.commentParameters because they can be nullable.
How can I create a map inplace like this instead of creating a variable and updating it where I want to keep the pair only in the case the value is not null?
I would just write my own factory methods for it, and call these methods instead.
fun <K, V> mapOfNullableValues(vararg pairs: Pair<K, V?>): Map<K, V> =
pairs.mapNotNull { (k, v) ->
if (v != null) k to v else null
}.toMap()
fun <K, V> mutableMapOfNullableValues(vararg pairs: Pair<K, V?>): MutableMap<K, V> =
pairs.mapNotNull { (k, v) ->
if (v != null) k to v else null
}.toMap(mutableMapOf())
By writing your own factory methods, you get to work with an array of Pairs, which is way easier than modifying the use site in place.
Note that compared to the built in builders, these methods create an extra list.
One way I found out without adding a lot of extra code would be to add code to add emptyList in the case it is null and remove them later.
Something like this:
val myMap: Map<Type, List<Parameter>> = mapOf(
Pair(Type.POST, request.postDetails?.postParameters ?: listOf()),
Pair(Type.COMMENT, request.commentDetails?.commentParameters ?: listOf())//commentDetails can be nullable
).filter{ it.value.isNotEmpty() }

Merge nested maps with similar outer keys, keeping both inner map's key value pairs

I would like to merge two nested maps as per below, without overwriting the data of the outer map's values, or by replacing the inner map, and overwriting the data already there.
Ideally within the nested map, I would keep the key/value pairs I already have, and add to that with the second map.
val mapOne: MutableMap<String, MutableMap<String, String>> = mutableMapOf(
"DirectoryOne" to mutableMapOf(
"FeatureOne" to "SomeUniqueStringFeature1",
"FeatureTwo" to "SomeUniqueStringFeature2"
)
)
val mapTwo: MutableMap<String, MutableMap<String, String>> = mutableMapOf(
"DirectoryOne" to mutableMapOf(
"FeatureThree" to "SomeUniqueStringFeature3"
)
)
The result I need would look as follows:
val mapOne: MutableMap<String, MutableMap<String, String>> = mutableMapOf(
"DirectoryOne" to mutableMapOf(
"FeatureOne" to "SomeUniqueStringFeature1",
"FeatureTwo" to "SomeUniqueStringFeature2",
"FeatureThree" to "SomeUniqueStringFeature3"
)
)
Just to elaborate some into reasoning behind this. I am planning on building the map dynamically at runtime and add features to a single map as they are required to be added throughout the duration of the application.
If you want to avoid the awkward use of the Java 8 merge function, you can use getOrPut. This is also more efficient because of the function inlining.
Also, according to Kotlin coding conventions, it's preferable to use a regular for loop instead of forEach (unless you're calling it on a nullable or at the end of a chain of collection operators).
for ((key, map) in mapTwo) {
mapOne.getOrPut(key, ::mutableMapOf).putAll(map)
}
Generalized:
fun <K1, K2, V> MutableMap<in K1, MutableMap<K2, V>>.mergeIn(other: Map<out K1, Map<out K2, V>>) {
for ((key, map) in other) {
getOrPut(key, ::mutableMapOf).putAll(map)
}
}
I was able to perform the desired action with the below code. Iterating over the outer key/value pairs and merging the nested key/value pairs with a BiConsumer/BiFunction to mapOne. (Typing got a bit burdensome)
mapTwo.forEach(BiConsumer { k: String, v: MutableMap<String, String> ->
mapOne.merge(k, v,
BiFunction { v1: MutableMap<String, String>, v2: MutableMap<String, String> ->
v1.putAll(v2)
v1
})
})
I soon discovered that I could more easily employ a lambda (Removing the BiConsumer/BiFunction) and then move the lambda out of the merge parenthesis. The final optimal solution is shown below.
mapTwo.forEach { (k: String, v: MutableMap<String, String>) ->
mapOne.merge(k, v
) { v1: MutableMap<String, String>, v2: MutableMap<String, String> ->
v1.putAll(v2)
v1
}
}
I am wrapping the above snippet in a function/method to be able to apply this multiple times where needed.

Generic transpose (or anything else really!) in Kotlin

Working on an Advent of Code puzzle I had found myself defining a function to transpose matrices of integers:
fun transpose(xs: Array<Array<Int>>): Array<Array<Int>> {
val cols = xs[0].size // 3
val rows = xs.size // 2
var ys = Array(cols) { Array(rows) { 0 } }
for (i in 0..rows - 1) {
for (j in 0..cols - 1)
ys[j][i] = xs[i][j]
}
return ys
}
Turns out that in the following puzzle I also needed to transpose a matrix, but it wasn't a matrix of Ints, so i tried to generalize. In Haskell I would have had something of type
transpose :: [[a]] -> [[a]]
and to replicate that in Kotlin I tried the following:
fun transpose(xs: Array<Array<Any>>): Array<Array<Any>> {
val cols = xs[0].size
val rows = xs.size
var ys = Array(cols) { Array(rows) { Any() } } // maybe this is the problem?
for (i in 0..rows - 1) {
for (j in 0..cols - 1)
ys[j][i] = xs[i][j]
}
return ys
}
This seems ok but it isn't. In fact, when I try calling it on the original matrix of integers I get Type mismatch: inferred type is Array<Array<Int>> but Array<Array<Any>> was expected.
The thing is, I don't really understand this error message: I thought Any was a supertype of anything else?
Googling around I thought I understood that I should use some sort of type constraint syntax (sorry, not sure it's called like that in Kotlin), thus changing the type to fun <T: Any> transpose(xs: Array<Array<T>>): Array<Array<T>>, but then at the return line I get Type mismatch: inferred type is Array<Array<Any>> but Array<Array<T>> was expected
So my question is, how do I write a transpose matrix that works on any 2-dimensional array?
As you pointed out yourself, the line Array(cols) { Array(rows) { Any() } } creates an Array<Array<Any>>, so if you use it in your generic function, you won't be able to return it when Array<Array<T>> is expected.
Instead, you should make use of this lambda to directly provide the correct value for the correct index (instead of initializing to arbitrary values and replacing all of them):
inline fun <reified T> transpose(xs: Array<Array<T>>): Array<Array<T>> {
val cols = xs[0].size
val rows = xs.size
return Array(cols) { j ->
Array(rows) { i ->
xs[i][j]
}
}
}
I don't really understand this error message: I thought Any was a supertype of anything else?
This is because arrays in Kotlin are invariant in their element type. If you don't know about generic variance, it's about describing how the hierarchy of a generic type compares to the hierarchy of their type arguments.
For example, assume you have a type Foo<T>. Now, the fact that Int is a subtype of Any doesn't necessarily imply that Foo<Int> is a subtype of Foo<Any>. You can look up the jargon, but essentially you have 3 possibilities here:
We say that Foo is covariant in its type argument T if Foo<Int> is a subtype of Foo<Any> (Foo types "vary the same way" as T)
We say that Foo is contravariant in its type argument T if Foo<Int> is a supertype of Foo<Any> (Foo types "vary the opposite way" compared to T)
We say that Foo is invariant in its type argument T if none of the above can be said
Arrays in Kotlin are invariant. Kotlin's read-only List, however, is covariant in the type of its elements. This is why it's ok to assign a List<Int> to a variable of type List<Any> in Kotlin.

Kotlin: maxBy{} with optimum-value

Let's say I have the following code in Kotlin:
val min = listOf("hello", "", "teeeeeest").minBy { it.length }
What I understand from the implementation of minBy is that it tracks minValue in a variable and iterates through the whole collection and updates it once it finds an even smaller element.
In the case of Strings though, we know that no element can have a value smaller than 0, therefore the empty String "" is optimal and the iteration can be stopped.
Is there a way I can tell minBy (or maxBy) the optimal value so it can stop once that is reached? If not, how can I implement this most easily?
There's no function in the stdlib that can do this, but you can implement it as an extension function yourself.
By using the non-local return feature of inline lambda functions in Kotlin, you can implement it like this:
fun <T, E : Comparable<E>> Iterable<T>.minBy(theoreticalMinimum: E, keySelector: (T) -> E): T? =
minBy {
val key = keySelector(it)
if (key <= theoreticalMinimum) return it // Non-local return.
else key
}
Now you can use it like this, and it will never visit "teeeeeest":
val min = listOf("hello", "", "teeeeeest").minBy(theoreticalMinimum = 0) { it.length }