What is the function of the * operation in Kotlin? - kotlin

The Code A is from the offical sample project.
I don't understand what val tasks = remember { mutableStateListOf(*allTasks) } mean, could you tell me ?
BTW, Android Studio give me some information, you can see Image A
Code A
#Composable
fun Home() {
// String resources.
val allTasks = stringArrayResource(R.array.tasks)
val allTopics = stringArrayResource(R.array.topics).toList()
// The currently selected tab.
var tabPage by remember { mutableStateOf(TabPage.Home) }
// True if the whether data is currently loading.
var weatherLoading by remember { mutableStateOf(false) }
// Holds all the tasks currently shown on the task list.
val tasks = remember { mutableStateListOf(*allTasks) }
...
}
Image A

From the documentation of varargs:
When you call a vararg -function, you can pass arguments individually, for example asList(1, 2, 3). If you already have an array and want to pass its contents to the function, use the spread operator (prefix the array with *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
As you see, it expands an array to multiple values for use in a vararg. If you havd an array containing the elements 1, 2, 3, you can pass *yourArray to a method that is equivalent to yourMethod(1,2,3).

In Kotlin * is the Spread Operator.
From docs :
When you call a vararg -function, you can pass arguments individually, for example asList(1, 2, 3). If you already have an array and want to pass its contents to the function, use the spread operator (prefix the array with *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
In this case tasks will contain the list of strings from R.array.tasks

Related

How did this code using zip() got the indexes of the elements in the list?

I'm solving exercises for a programming book in Kotlin. The task is to implement function using "zip()" and return a "List" of Pairs, where the first item in a "Pair" is the element, and the second item is the index of that element.
I solved the exercise, the solution works but I cannot understand the book solution.
Here is mine solution:
fun zipWithIndex(listToTake: List<Any>): List<Pair<Any, Any>> {
val finalList = mutableListOf<Any>()
var num = 0
for(element in listToTake) {
finalList += num
num ++
}
return (listToTake zip finalList)
}
fun main() {
val listToCall = listOf<String>("a", "b", "c")
println(zipWithIndex(listToCall))
}
And here is the book solution:
fun <T> List<T>.zipWithIndex(): List<Pair<T, Int>> =
zip(indices)
fun main() {
val list = listOf('a', 'b', 'c')
list.zipWithIndex() eq
"[(a, 0), (b, 1), (c, 2)]"
}
Can somebody please explain how does the book solution get the indexes of the elements in the list or tell me the topic that I need to read about to figure out how the code from the book works.
Thanks in advance for any help.
indices is a property of every kotlin List: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/#extension-properties
It's an IntRange of all valid indices, so essentially the range (https://kotlinlang.org/docs/ranges.html) equivalent of [0, 1, 2]. An IntRange is an Iterable, so it can be zipped with (the third zip overload in the api docs of list).
So it is equivalent to the zip you did, except you constructed [0, 1, 2] yourself while they used the pre-existing property of the List.
They also defined an extension function on List (https://kotlinlang.org/docs/extensions.html#extension-functions) instead of passing the list as a parameter.

Initializing a list in kotlin

I am trying to initlaise a list as part of a constructor argument. But I do not understand what it wants from me
distortion = List(
size = 10,
init = 0
)
The list is of type room entity
var distortion: List<DistortionCacheEntity>? = null
The init part is underlined in red. I dont know what to put in there or how to write it. Could someone help.
The function you are trying to invoke looks like this:
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T>
So init here is not an Integer but rather a function. Invoke like so:
distortion = List(size = 10) { index : Int ->
//Create object that the list needs to hold
}
var distortion: List<DistortionCacheEntity>? = null
distortion = List(10) { index -> DistortionCacheEntity()}
The simplest way to initialize a list is with kotlin library function listOf(...)
For example,
listOf(1, 2, 3) // [1, 2, 3]

Difference between plus() vs add() in Kotlin List

I am new to Kotlin. I want to know the difference between plus() and add() in Kotlin List.
fun main() {
val firstList = mutableListOf("a", "b")
val anotherList = firstList.plus("c") // creates a new list and returns it. firstList is not changed
println(firstList) // [a, b]
println(anotherList) // [a, b, c]
val isAdded = firstList.add("c") // adds c to the mutable variable firstList
println(firstList) // [a, b, c]
println(isAdded) // true
val unmodifiableList = listOf("a", "b")
val isAdded2 = unmodifiableList.add("c") // compile error, add is not defined on an UnmodifiableList
}
plus creates a new List out of an existing list and a given item or another list and returns the result (the new created List), while the input List is never changed. The item is not added to the existing List.
add is only defined on modifiable Lists (while the default in Kotlin is an ImmutableList) and added the item to the existing List and returns true to indicate that the item was added successfully.
Basically:
plus() adds element and returns the list containing this new value(s).
Returns a list containing all elements of the original collection and then the given [element].
So with plus():
val list1 = listOf(1,2,3)
val list2 = list1.plus(4) // [1, 2, 3, 4]
val list3 = listOf(0).plus(list2) // [0, 1, 2, 3, 4]
add() just adds an element and returns bool.
Adds the specified element to the end of this list. Return true because the list is always modified as the result of this operation.
These two are completely different functions.
First of all add() can only add one item to mutable collection while plus() can add one item of a collection of items.
Second - add() function returns true or false depending on whether the collection was modified or not, while plus() returns result immutable collection.
The third and most important difference it that - the plus() function is a operator function overload which means that it is evaluated as static and can be used like this
val result = listOf(1,2,3) + 4 // [1,2,3,4]
val result2 = listOf(1,2,3).plus(4) // [1,2,3,4]
val result3 = listOf(1,2,3) + listOf(4,5,6) // [1, 2, 3, 4, 5, 6]
val result4 = listOf(1,2,3).plus(listOf(4,5,6)) // [1, 2, 3, 4, 5, 6]
While add() is just a regular instance function of MutableCollection interface
If you come from Java world you need to keep in mind that default List in Kotlin is immutable. It means that you can't just add elements to this list.
In Kotlin, plus (+) and minus (-) operators are defined for
collections. They take a collection as the first operand; the second
operand can be either an element or another collection. The return
value is a new read-only collection.
But Kotlin also has MutableList(like an usual Java List) where you can add elements to the mutable collection
val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)

How to create variables in a for-loop with Kotlin

Given a maximum list size in parameter size and total amount of elements in parameter elements, I need to create a list of lists. What is the syntax for creating variables in for loops in Kotlin?
The way I'm thinking of going about this is to declare and create lists before elements are added to a list. Then, when a list has reached full capacity, it is switched out for the next list that is empty.
Here is the half-baked code:
fun listOfLists(size: Int, vararg elements: String): List<List<String>> {
var amountOfElements = elements.size
var currentSubList: List<String> = mutableListOf<String>()
val numberOfLists: Int = amountOfElements / size + 1
for (n in 0..numberOfLists) {
// Code for creating the total number of lists needed
}
for (e in elements) {
if (amountOfElements % size == 0) {
// Code for switching lists
}
amountOfElements--
}
As #dyukha correctly mentioned, what you need is chunked() function.
fun listOfLists(size: Int, vararg elements: String) =
elements.asList().chunked(size)
Or, if you want to be really efficient, you can also use asSequence():
fun listOfLists(size: Int, vararg elements: String) =
elements.asSequence().chunked(size)
chunked() doesn't work on Array, because it's defined on Iterable and Sequence, and Array doesn't implement any of them.

How can I tell Kotlin that an array or collection cannot contain nulls?

If I create an array, then fill it, Kotlin believes that there may be nulls in the array, and forces me to account for this
val strings = arrayOfNulls<String>(10000)
strings.fill("hello")
val upper = strings.map { it!!.toUpperCase() } // requires it!!
val lower = upper.map { it.toLowerCase() } // doesn't require !!
Creating a filled array doesn't have this problem
val strings = Array(10000, {"string"})
val upper = strings.map { it.toUpperCase() } // doesn't require !!
How can I tell the compiler that the result of strings.fill("hello") is an array of NonNull?
A rule of thumb: if in doubts, specify the types explicitly (there is a special refactoring for that):
val strings1: Array<String?> = arrayOfNulls<String>(10000)
val strings2: Array<String> = Array(10000, {"string"})
So you see that strings1 contains nullable items, while strings2 does not. That and only that determines how to work with these arrays:
// You can simply use nullability in you code:
strings2[0] = strings1[0]?.toUpperCase ?: "KOTLIN"
//Or you can ALWAYS cast the type, if you are confident:
val casted = strings1 as Array<String>
//But to be sure I'd transform the items of the array:
val asserted = strings1.map{it!!}
val defaults = strings1.map{it ?: "DEFAULT"}
Why the filled array works fine
The filled array infers the type of the array during the call from the lambda used as the second argument:
val strings = Array(10000, {"string"})
produces Array<String>
val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null })
produces Array<String?>
Therefore changing the declaration to the left of the = that doesn't match the lambda does not do anything to help. If there is a conflict, there is an error.
How to make the arrayOfNulls work
For the arrayOfNulls problem, they type you specify to the call arrayOfNulls<String> is used in the function signature as generic type T and the function arrayOfNulls returns Array<T?> which means nullable. Nothing in your code changes that type. The fill method only sets values into the existing array.
To convert this nullable-element array to non-nullable-element list, use:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.filterNotNull()
val upper = strings.map { it.toUpperCase() } // no !! needed
Which is fine because your map call converts to a list anyway, so why not convert beforehand. Now depending on the size of the array this could be performant or not, the copy might be fast if in CPU cache. If it is large and no performant, you can make this lazy:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asSequence().filterNotNull()
val upper = strings.map { it.toUpperCase() } // no !! needed
Or you can stay with arrays by doing a copy, but really this makes no sense because you undo it with the map:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings: Array<String> = Array(nullableStrings.size, { idx -> nullableStrings[idx]!! })
Arrays really are not that common in Java or Kotlin code (JetBrains studied the statistics) unless the code is doing really low level optimization. It could be better to use lists.
Given that you might end up with lists anyway, maybe start there too and give up the array.
val nullableStrings = listOf("a","b",null,"c",null,"d")
val strings = nullableStrings.filterNotNull()
But, if you can't stop the quest to use arrays, and really must cast one without a copy...
You can always write a function that does two things: First, check that all values are not null, and if so then return the array that is cast as not null. This is a bit hacky, but is safe only because the difference is nullability.
First, create an extension function on Array<T?>:
fun <T: Any> Array<T?>.asNotNull(): Array<T> {
if (this.any { it == null }) {
throw IllegalStateException("Cannot cast an array that contains null")
}
#Suppress("CAST_NEVER_SUCCEEDS")
return this as Array<T>
}
Then use this function new function to do the conversion (element checked as not null cast):
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asNotNull() // magic!
val upperStrings = strings.map { it.toUpperCase() } // no error
But I feel dirty even talking about this last option.
There is no way to tell this to the compiler. The type of the variable is determined when it is declared. In this case, the variable is declared as an array that can contain nulls.
The fill() method does not declare a new variable, it only modifies the contents of an existing one, so it cannot cause the variable type to change.