I can convert a List<Int?> to List<Int> using mapNotNull function as shown below.
#Test
fun main() {
val testData = listOf(1, null, 3, null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: List<Int?>): List<Int> {
return testData.mapNotNull{ it }
}
How could I convert Map<String, Int?> to Map<String, Int>?
The below with testData.filter { it.value != null } doesn't works, as it still produce Map<String, Int?>.
#Test
fun main() {
val testData = mapOf("One" to 1, "Two" to null, "Three" to 3, "Four" to null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: Map<String, Int?>): Map<String, Int> {
return testData.filter { it.value != null }
}
Well, not really out of the box (in the sense that you get Map<String, Int> immediately), but what about filterValues?
testData.filterValues { it != null } // gives Map<String, Int?> but without null-values
Combining or replacing that with mapValues (maybe you can use a default value instead of null?):
// combining:
testData.filterValues { it != null }
.mapValues { (_, value) -> value as Int }
// replacing:
testData.mapValues { (_, value) -> value ?: /* default value */ 0 }
Both give a Map<String, Int> but the first creates and fills 2 maps under the hood and the second uses 0 instead of null.
You can also simplify the filterValues-variant with an appropriate unchecked cast, as "we know it better":
testData.filterValues { it != null } as Map<String, Int> // unchecked cast, because: we really do know better, do we? ;-)
Alternatively, you could also just handle all entries the way you knew already (using mapNotNull) and then create a new map out of it:
testData.asSequence()
.mapNotNull { (key, value) ->
value?.let {
key to it
}
}
.toMap() // giving Map<String, Int>
If you require that more often you may even want to have your own extension function in place:
#Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
Now you can use it similar as to follows:
testData.filterValuesNotNull() // giving Map<String, Int>
Possible alternative with custom helper function:
inline fun <K, V, R> Map<K, V>.mapValuesNotNullToMap(transformValue: (V) -> R?): Map<K, R> =
buildMap {
this#mapValuesNotNullToMap.entries.forEach { (key, value) ->
transformValue(value)?.let { put(key, it) }
}
}
Related
How can I rewrite this code without having to resort to a MutableMap and conversion to immutable map?
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val map = mutableMapOf("key1" to mandatoryValue)
optionalValue?.let { map.put("key2", it) }
return map.toMap()
}
My alternative solution isn't very nice either because it needs an unsafe cast:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf(
"key1" to mandatoryValue,
"key2" to optionalValue
).filterValues { it != null } as Map<String, String>
What I am looking for is something in the lines of:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf(
"key1" to mandatoryValue,
optionalValue?.let { "key2" to it }
)
You can have your own extension function as follows and then use it to filter null values from a Map:
fun <K, V> Map<K, V?>.filterValuesNotNull() =
mapNotNull { (k, v) -> v?.let { k to v } }.toMap()
toMap() does not necessarily create an immutable map. It is only guaranteed to be read-only. The underlying class instance might be a MutableMap (which in the current implementation is true if it has more than one key). Therefore, toMap() in your first block of code is unnecessary. The MutableMap is automatically upcast to Map when you return it since you specified Map as the return type. So, you could have put
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val map = mutableMapOf("key1" to mandatoryValue)
optionalValue?.let { map.put("key2", it) }
return map
}
or
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mutableMapOf("key1" to mandatoryValue).apply {
if (optionalValue != null) put("key2", optionalValue)
}
To get the syntax you requested in your last example, you could create an overload of mapOf that accepts and filters null values:
fun <K, V> mapOf(vararg pairs: Pair<K, V>?): Map<K, V> =
mapOf(*pairs.filterNotNull().toTypedArray())
There's nothing wrong with using MutableMap - in fact your first solution (without the redundant toMap()) is already pretty elegant. It's simpler and clearer than any immutable answer will be. Immutable operations come at the cost of additional object creations and copies, so unless you need the guarantees of immutability, it's best to use a MutableMap but only expose it via the Map interface, as you are already doing.
If you really wanted to do it immutably, you could do it like this:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf("key1" to mandatoryValue) +
(optionalValue?.let { mapOf("key2" to it) } ?: emptyMap())
Or equivalently if you prefer:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf("key1" to mandatoryValue) +
if (optionalValue != null) mapOf("key2" to optionalValue) else emptyMap()
Or:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val mandatoryMap = mapOf("key1" to mandatoryValue)
return optionalValue?.let { mandatoryMap + ("key2" to optionalValue) } ?: mandatoryMap
}
Or:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val mandatoryMap = mapOf("key1" to mandatoryValue)
return if (optionalValue != null) mandatoryMap + ("key2" to optionalValue) else mandatoryMap
}
In kotlin 1.6
buildMap {
put("key1", mandatoryValue)
if (optionalValue != null)
put("key2", optionalValue)
}
This creates a mutableMap under the sheets, but is quite elegant and readable <3
Here's the Kotlin code I'd like to write (with extra type annotations for clarity):
fun test(alpha: String, beta: String, gamma: String? = null, delta: String? = null) {
val r1: Map<String, String?> =
hashMapOf(
"alpha" to alpha,
"beta" to beta,
"gamma" to gamma,
"delta" to delta
)
val r2: Map<String, String> = r1.filterValues { it != null }
callSomeFunction(r2) // expects a parameter of type Map<String, String>
}
Unfortunately, r1.filterValues { it != null } gives me back a Map<String, String?>, not a Map<String, String>. I understand why this is; it's the same reason that listOf(1, null).filter { it != null } has a different type from listOf(1, null).filterNotNull(). However, I still need to solve my problem!
Is there an idiomatic way to "filterValuesNotNull" from a Map?
Or, stepping up a level, is there some other idiomatic way to say "give me a Map of the following arguments, but skip those whose values are null"? I could resort to this, but I don't want to:
fun test(alpha: String, beta: String, gamma: String? = null, delta: String? = null) {
var r1: MutableMap<String, String> = mutableMapOf(
"alpha" to alpha,
"beta" to beta
)
if (gamma != null) {
r1["gamma"] = gamma
}
if (delta != null) {
r1["delta"] = delta
}
callSomeFunction(r1) // expects a parameter of type Map<String, String>
}
The functions that in my opinion should be in the stdlib:
fun <K,V: Any> Map<K,V?>.filterNotNullValuesTo(destination: MutableMap<K,V>): Map<K, V> {
for ((key, value) in entries) if (value != null) destination[key] = value
return destination
}
fun <K,V: Any> Map<K,V?>.filterNotNullValues(): Map<K,V> = filterNotNullValuesTo(mutableMapOf())
Here's 2 ways to do it, although whether either is idiomatic, I do not know.
#Suppress("UNCHECKED_CAST)
fun filterNotNullUnchecked(map: Map<String, String?>): Map<String, String> =
map.filterValues { it != null } as Map<String, String>
EDIT: As #Tenfour04 pointed out, this isn't unsafe, just unchecked because of type erasure, and adding a Suppress("UNCHECKED_CAST) annotation makes the warning go away.
fun filterNotNullUgly(map: Map<String, String?>): Map<String, String> {
val map2 = HashMap<String, String>()
for ((k, v) in map) if (v != null) map2[k] = v
return map2
}
hm, I suppose, it can be decided on that way
val newMap: Map<String, String> = r1.mapNotNull { (key, value) ->
value?.let { key to it }
}.toMap()
Using Kotlin 1.2.41-release and given a List<Pair<Int, Int>>, the following code generates a compilation error in Intellij, although Gradle command line build works.
sortedWith(compareBy({ it.first }, { it.second }))
Cannot choose among the following candidates without completing type inference.
public fun <T> compareBy(vararg selectors: (???) -> Comparable<*>?): kotlin.Comparator<???> defined in kotlin.comparisons
public fun <T> compareBy(vararg selectors: (Pair<Int, Int>) -> Comparable<*>?): kotlin.Comparator<Pair<Int, Int>> defined in kotlin.comparisons
How can I fix this?
Edit:
edges
.map {
it.either.run {
val p = this
val q = it.other(this)
val min = min(p, q)
if (min == p) p to q else q to p
}
}
.sortedWith(compareBy({ it.first }, { it.second }))
.toList()
where, edges is Iterable<Edge>
class Edge(private val v: Int, private val w: Int, val weight: Double) : Comparable<Edge> {
val either: Int
get() = v
fun other(vertex: Int): Int {
return if (v == vertex) w else v
}
override fun compareTo(other: Edge): Int {
return weight.compareTo(other.weight)
}
override fun toString(): String {
return "Edge(v=$v, w=$w, weight=$weight)"
}
}
I've a Map of (key, value) where the value is a predefined function.
I want to iterate the input param in the Mp and check where the key is matching with the input parameter, then invoke the equivalent function, something like this
My code required to be something like below:
fun fn1: Unit { // using Unit is optional
println("Hi there!")
}
fun fn2 {
println("Hi again!")
}
fun MainFun(x: int){
val map: HashMap<Int, String> = hashMapOf(1 to fn1, 2 to fn2)
for ((key, value) in map) {
// if key = x then run/invoke the function mapped with x, for example if x = 1 then invoke fn1
}
}
Notes: I read something like below, but could not know how to us them:
inline fun <K, V> Map<out K, V>.filter(
predicate: (Entry<K, V>) -> Boolean
): Map<K, V> (source)
val russianNames = arrayOf("Maksim", "Artem", "Sophia", "Maria", "Maksim")
val selectedName = russianNames
.filter { it.startsWith("m", ignoreCase = true) }
.sortedBy { it.length }
.firstOrNull()
Hi I hope this would help you.
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: IntArray){
val map = hashMapOf(
1 to ::fn1,
2 to ::fn2)
map.filterKeys { it == args[0] } // filters the map by comparing the first int arg passed and the key
.map { it.value.invoke() } // invoke the function that passed the filter.
}
If the keyis RegEx then map.filterKeys { Regex(it).matches(x) } can be used, below full example of it Try Kotlin:
data class Person(val name: String,
val age: Int? = null)
val persons = listOf(Person("Alice"),
Person("Bob", age = 23))
fun old() {
val oldest = persons.maxBy { it.age ?: 0 }
println("The oldest is: $oldest")
}
fun young() {
val youngest = persons.minBy { it.age ?: 0 }
println("The youngest is: $youngest")
}
fun selection(x: String) {
val map = mapOf(
"old|big" to ::old,
"new|young" to ::young)
map.filterKeys { Regex(it).matches(x) }
.map { it.value.invoke() }
}
fun main(args: Array<String>) {
selection("new")
}
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: Array<Int>){
val map = hashMapOf(1 to ::fn1, 2 to ::fn2)
map.forEach { key, function -> function.invoke() }
}
This will do the work but your code does not even have the correct syntax. You should learn the basic first.
I currently have this class with dsl like building ability
class GRLMessage {
var headerMap : MutableMap<String, String> = mutableMapOf()
lateinit var methodType : GRLMethod
lateinit var multipartObject : IGRLMultipart
fun message(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun method(closure: GRLMessage.() -> GRLMethod) : GRLMessage {
methodType = closure()
return this
}
fun headers(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
fun multipart(closure: GRLMessage.() -> IGRLMultipart) : GRLMessage {
multipartObject = closure()
return this
}
}
And I test it like this
class GRLMessageTest {
data class DummyMultipart(val field: String) : IGRLMultipart {
override fun getContent() {
this
}
}
#Test fun grlMessageBuilderTest() {
val grlMessage = GRLMessage().message {
method { GRLMethod.POST }
headers {
header { Pair("contentType", "object") }
header { Pair("objectType", "DummyMultipart") }
}
multipart { DummyMultipart("dummy") }
}
val multipart = DummyMultipart("dummy")
val headers = mapOf(
Pair("contentType", "object"),
Pair("objectType", "DummyMultipart")
)
val method = GRLMethod.POST
assertEquals(multipart, grlMessage.multipartObject)
assertEquals(method, grlMessage.methodType)
assertEquals(headers, grlMessage.headerMap)
}
}
But despite providing
header { Pair("contentType", "object") }
I still have to evaluate closure inside header method and directly put key and value into my MutableMap
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
Is there a better way adding entries to Map?
Does your headerMap need to be a var? If not, you can change it to a val and use headerMap += closure().
Adding an extension function makes your fluent methods more obviously fluent:
fun <T: Any> T.fluently(func: ()->Unit): T {
return this.apply { func() }
}
With that your fluent function is always clear about its return:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return fluently { headerMap += closure() }
}
Which is really the same as:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return this.apply { headerMap += closure() }
}
But the extension function adds a touch of readability.
Above I use the answer given in by #Ruckus for solving your specific question of adding a Pair to the headerMap. But you have other options that you might want to know about for other use cases of your DSL...
You can use let, apply or with which would allow any type of decomposition of the results of closure() call (maybe it is more complicated than Pair in the future). All of these are basically the same, minus their resulting value:
with(closure()) { headerMap.put(this.first, this.second) }
closure().apply { headerMap.put(this.first, this.second) }
closure().let { headerMap.put(it.first, it.second) }
Using let or apply is nice if you want to handle a case where closure() allows nullable return, in which case you might want to take action only if not null:
closure()?.apply { headerMap.put(this.first, this.second) }
closure()?.let { headerMap.put(it.first, it.second) }
Other notes about your code:
use val instead of var unless you have no other choice
lateinit (or the similar Delegates.notNull()) seem dangerous to use in an uncontrolled lifecycle where there is no guarantee it will be completed, because the error message will be confusing and happen at some unexpected time in the future. There are likely other ways to solve this with a DSL that chains calls to create more of a multi-step grammar
You can shorten code by only having types on one side of the assignment, for example:
val myMap = mutableMapOf<String, String>()
instead of
var myMap : MutableMap<String, String> = mutableMapOf()
Well for now as a solution I created extension for MutableMap
fun MutableMap<String, String>.put(pair : Pair<String, String>) {
this.put(pair.first, pair.second)
}
Which allowed me to write like this
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
headerMap.put(closure())
return this
}