Map withDefault not wokring kotlin? - kotlin

I have a mutable map in which I want a default value 0 for key not found
I tried the below code withDefault({somevalue})but still returns null
val attributes = mutableMapOf<String, Int>().withDefault({0})
attributes["orange"]=345
println(attributes["orange"])
println(attributes["orang"])
The result I get is 345 null instead of 345 0
What is it I am missing here?

withDefault is to be used with getValue and not with get resp. [...]:
val attributes = mutableMapOf<String, Int>().withDefault { 0 }
val result = attributes["orang"]
println(result) // Output: null
val result2 = attributes.getValue("orang")
println(result2) // Output: 0
See: withDefault
This implicit default value is used when the original map doesn't
contain a value for the key specified and a value is obtained with
Map.getValue function [...]

Related

TypeVariable(V) Required in Mutable Map assignment in Kotlin. Cannot put the value from a map to itself

The following example is a simplified version of what I am trying to do in my application.
fun main() {
val test: MutableMap<Int, String> = mutableMapOf(
1 to "Apple",
)
test[2] = test[1] // test[1] has incorrect type.
}
The code doesn't compile. IntelliJ gives the following hint:
Type mismatch.
Required: TypeVariable(V)
Found: String?
I don't understand what a TypeVariable is. but when I provide a default value the error disappears
test[2] = test[1] ?: "Grape"
Why the required type is TypeVariable(V), not String, and what exactly is it? What to do if there's no default value for my application purposes?
... = test[1]
returns a String? as the hint showed you. But test is a MutableMap of <Int, String>, which means you need to assign a String:
... = test[1]!!
Of course this will only work if 1 is a valid key in test. Otherwise your code with the null safety operator is the way to go:
... = test[1] ?: "default value"

Kotlin iterate a collection and map a certain value to new map

I have a map of collections . I need to get a list of ids from that..
val m1 = mapOf("id" to 1, "name" to "Alice")
val m2 = mapOf("id" to 2, "name" to "Bob")
val m3 = mapOf("id" to 3, "name" to "Tom")
val nameList = listOf(m1, m2, m3)
The result shall be [1, 2, 3]
Assuming you want a list as per the example, not a map as per the title, I would do it like this:
val result = nameList.map {
it.getValue("id").also { id ->
require(id is Int) { "id must be an Int" }
} as Int
}
This has the advantage of handling the following errors cleanly:
The id key is missing: NoSuchElementException: Key id is missing in the map
The id value is not an Int: IllegalArgumentException: id must be an Int
First things first, I believe that if you can, you should use classes instead of maps for storing heterogeneous data like this. So instead of your maps, you can use:
data class Person(val id: Int, val name: String)
val m1 = Person(id = 1, name = "Alice")
val m2 = Person(id = 2, name = "Bob")
val m3 = Person(id = 3, name = "Tom")
val list = listOf(m1, m2, m3)
val idsList = list.map { it.id } // no error handling required, rely on the type system
Now, if you really want to use maps like that, you have several options.
If you're certain the id key will be present and its value will be an Int, you can use the following:
nameList.map { it["id"] as Int }
This will fail with NullPointerException if id is not present in one of the maps or with ClassCastException if it's not an Int.
Normally you should make sure your map matches your contract at creation time, and not when accessing this kind of information.
But if you need to handle errors here for some reason, you can use the following instead:
nameList.map {
(it.getValue("id") as? Int) ?: error("'id' is not an Int")
}
getValue fails on absent keys with NoSuchElementException, and the error() call fails with IllegalStateException. You can also use other kinds of exceptions using throw or require().
If you want to just ignore the entries that don't have a valid integer id, you can use the following:
nameList.mapNotNull { it["id"] as? Int }
If you want to ignore the entries that don't have an id, but fail on those who have a non-integer id, you can use this:
nameList.mapNotNull { map ->
map["id"]?.let { id ->
(id as? Int) ?: error("'id' is not an Int")
}
}
These 2 last examples rely on mapNotNull, which filters the elements out if their mapped value is null.

Why filtering out null map keys doesn't change type to not nullable in kotlin?

I have a list of objects with an optional id as String and I want to make a map out of it.
I want to have the keys of my map as non nullable: so something like this:
data class Foo(
val id: String? = null
val someStuff: String? = null,
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo.filterNot { it.id == null }.associateBy { it.id }
Here bar type is Map<String?, Foo> but not Map<String, Foo>
My workaround is to add a non null asserted call: !!, but it doesn't seem clean.
Is there an easy and safe way to do this?
This looks like something that contracts could help with, but currently a contract expression can't access properties of the class in use.
As a workaround, you could define a 2nd class that has a non-null id, like so
data class Foo(
val id: String? = null,
val someStuff: String? = null
)
data class Foo2(
val id: String,
val someStuff: String? = null
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo
.mapNotNull { if (it.id != null) Foo2(it.id, it.someStuff) else null }
.associateBy { it.id }
There's a six-year-old open feature request for Map.filterNotNullKeys() and a four-year old open feature request for Map.associateByNotNull().
In my opinion, the associateBy { it.id!! } would be cleanest for readability. But you could do it like this:
val bar = foo.mapNotNull { it.id?.run { it.id to it } }.toMap()
As for your actual question, that logic is way too many steps for the compiler to infer. Your last function call to associateBy sees a nullable, so it infers a nullable. For the compiler to figure this out, it would have to step back and see that the List that you call associateBy on happens to have filtered out certain objects in a way that happens to ensure that a certain nullable property won't be null within this specific list, and it's the same property that you are associating with. Now imagine it has to do this for every call to any generic function, and the various lambdas involved could potentially have multiple lines of code. Compile times would skyrocket.

Check if a list contains at least one variable non null

I'm making my first app in Kotlin and there is a lot of syntax I don't know, and I was wondering if there is a better way to check if a list contains at least one non null entry.
For now my solution is:
var atLeastOneValue: Boolean
var i = 0
for (x in list) {
if (x != null) atLeastOneValue = true
else i++
}
if (list.size == i) atLeastOneValue = false
return atLeastOneValue
I'm working with MutableList<String>.
You can use contains function for that:
val hasNull = list.contains(null)
contains can also be called in the operator form, it corresponds to the operator in:
val hasNull = null in list
val hasNoNull = null !in list

Easiest way of defining and using of Global Variable

"first part" &&&& fun _ ->
let ident
"second part" &&&& fun _ ->
ident ....
I need to use variable "ident".
I just need to pass value of variable from first part of test to second one...
I want to ask you if there is any easy way how to define and use global variable or even if you have better (and easy) idea of doing that
Keep in mind, please, that I am a beginner, so I would prefer easier ones.
Global variables will often make your code difficult to work with - particularly if they are mutable.
Instead, consider returning the values you need to keep track of as composite values. An easy data type to start with would be a tuple:
let ``first part`` id =
let someOtherValue = "Foo"
someOtherValue, id + 1
This function takes an int (the current ID) as input, and returns string * int (a tuple where the first element is a string, and the second element and int) as output.
You can call it like this:
> let other, newId = ``first part`` 42;;
val other : string = "Foo"
val newId : int = 43
Notice that you can use pattern matching to immediately destructure the values into two named symbols: other and newId.
Your second function could also take an ID as input:
let ``second part`` id otherArgument =
// use id here, if you need it
"Bar"
You can call it like this, with the newId value from above:
> let result = ``second part`` newId "Baz";;
val result : string = "Bar"
If you find yourself doing this a lot, you can define a record for the purpose:
type Identifiable<'a> = { Id : int; Value : 'a }
Now you can begin to define higher-order functions to deal with such a type, such as e.g. a map function:
module Identifiable =
let map f x = { Id = x.Id; Value = f x.Value }
// Other functions go here...
This is a function that maps the Value of an Identifiable from one value to another, but preserves the identity.
Here's a simple example of using it:
> let original = { Id = 42; Value = "1337" };;
val original : Identifiable<string> = {Id = 42;
Value = "1337";}
> let result' = original |> Identifiable.map System.Int32.Parse;;
val result' : Identifiable<int> = {Id = 42;
Value = 1337;}
As you can see, it preserves the value 42, but changes the Value from a string to an int.
You can still change the ID explicitly, if you want to do that:
> let result'' = { result' with Id = 7 };;
val result'' : Identifiable<int> = {Id = 7;
Value = 1337;}
Since this was getting out of hand for comments this is how I would do it for an example
let mutable t = 0
let first =
t <- 1 + 1
//other stuff
let second =
//can use t here and it will have a value of 2
In some cases you have to use a ref:
let t = ref 0
let first =
t := 1 + 1
//other stuff
let second =
//can use t here and it will have a value of 2 -
// you use "!t" to get the value
If you define ident at the top of your file like this :
let ident = "foo"
// rest of your code using ident
ident are global and you can use in the next part of your file.
EDIT :
If ident wil change in the next part of your code, use this :
let ident = ref "foo"