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)
Related
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
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.
Given a Map or MutableMap:
val scores: MutableMap<String, Int> = mutableMapOf(
"some person" to 0,
"some other person" to 0,
"you" to 0,
"me" to 0
)
I am unable to increment these as I would in Python, and I'm not sure what the proper way to do it is, or if it is even possible.
fun test() {
scores["you"] += 2
}
This gives the error:
Operator call corresponds to a dot-qualified call 'scores["you"].plusAssign(2)' which is not allowed on a nullable receiver 'scores["you"]'.
I'm not sure what's going on here.
The top answer contains false information. It does not always look for the plusAssign operator, and you can use indexing along with += in some cases.
According to the actual doc, it first looks for plusAssign, and then uses it if there is no plus operator function available, if there is no plusAssign available, it tries to generate code like: a = a + b.
If you were to use an ArrayList, it can work, for example:
val arr = arrayListOf(1, 2, 3, 4)
arr[0] += 4 // arr[0] = arr[0] + 4 -> arr.set(0, arr.get(0) + 4)
arr // 5, 2, 3, 4
The only problem is that map[key] returns a null reference when the key does not map to a value in the map. So here's another way of doing it, without a dot qualified call:
val map = hashMapOf(1 to 2, 3 to 4)
// map += 1 to map[1]!! + 2 // If you are sure about the key
map += 1 to ((map[1] ?: 0) + 2) // map.put(1, (map.get(1) ?: 0) + 2)
map // { 1: 4, 3: 4 }
You don't even need to use the merge method, simply add an extension function:
operator fun Int?.plus(other: Int) = this?.plus(other) ?: other
fun main() {
val map = hashMapOf(1 to 2, 3 to 4)
map[1] += 2 // map[1] = map[1] + 2 -> map.put(1, map.get(1).plus(2))
}
The += operator is a shorthand operator for the plusAssign function (doc), and is used for adding or replacing entries to a mutable map or collection, as specified by the Kotlin doc for map operations
You can also add new entries to maps using the shorthand operator
form. There are two ways:
plusAssign (+=) operator.
the [] operator alias for set().
When you use scores["you"] += 2, since += is a shorthand operator for plusAssign, and scores["you"] can return a nullable value (as can any get operation on a map), you get the compilation error
Operator call corresponds to a dot-qualified call 'scores["you"].plusAssign(2)' which is not allowed on a nullable receiver 'scores["you"]'.
For your use-case, you can better use the merge method (doc) on the map as below
scores.merge("you", 2, Int::plus)
Do note that merge would add an entry to the map even if the key is absent in the map. In case you want to increment the value for the key only if it's present in the map, you can use the computeIfPresent method (doc) as below
scores.computeIfPresent("you"){ _, v -> v + 2 }
I find Lambda in Kotlin to be very confusing and on the top of it is "it".
There are two things I know about "it" and i.e.
If your Lambda has their own argument, you can replace its name with "it".
"It" is an automatically generated name for your Lambda, if it has
only one argument, and you don't specify a different argument name.
Still I don't understand what actually passes as "it".
For E.g. I wanted to apply modulo function on each element of a 3x3 matrix.
fun main(){
var result = Array(3) {
IntArray(3) { 3;2;4;6;7;9;12;11;23 }
}
result = Array(3){ IntArray(3) {it%2} }
println(result.joinToString("\n") { it.joinToString(" ") })
}
Here I assumed that "it" takes each element of the matrix which is clearly not the case as my output was:
0 1 0
0 1 0
0 1 0
So can you please explain me how "it" works, what is happening here? and what would be the correct way to implement this program?
Your line
result = Array(3){ IntArray(3) {it%2} }
isn't doing anything to the original Array that result is pointing at. You are creating a brand new group of array objects by calling the Array and IntArray constructors.
The lambda that you pass to the IntArray constructor has an input parameter that represents the array index, and the return value of your lambda is what will be put into the array at that index. So in this case it is the array index, and your lambda is returning 0 and 1 for even and odd indices respectively.
You are also instantiating your array incorrectly to begin with. Your lambda that you pass to that IntArray constructor is throwing away a bunch of pointless Int values and then returning 23 for each item. So you've created a 3x3 matrix that is completely filled with the number 23.
The correct syntax for creating an array with explicit values is to use arrayOf or intArrayOf.
val result = arrayOf(
intArrayOf(3, 2, 4),
intArrayOf(6, 7, 9),
intArrayOf(12, 11, 23)
)
To modify all the values of an array, you typically iterate the traditional way, not with a lambda:
for (innerArray in result) {
for (i in innerArray.indices)
innerArray[i] = innerArray[i] % 2
}
You were probably thinking of the map function, which lets you pass a lambda and returns a new List with the lambda function applied to every element of the input collection. Or when working with collections other than arrays, you can use forEach or onEach to iterate them without modifying them.
I am pretty confused with both functions fold() and reduce() in Kotlin, can anyone give me a concrete example that distinguishes both of them?
fold takes an initial value, and the first invocation of the lambda you pass to it will receive that initial value and the first element of the collection as parameters.
For example, take the following code that calculates the sum of a list of integers:
listOf(1, 2, 3).fold(0) { sum, element -> sum + element }
The first call to the lambda will be with parameters 0 and 1.
Having the ability to pass in an initial value is useful if you have to provide some sort of default value or parameter for your operation. For example, if you were looking for the maximum value inside a list, but for some reason want to return at least 10, you could do the following:
listOf(1, 6, 4).fold(10) { max, element ->
if (element > max) element else max
}
reduce doesn't take an initial value, but instead starts with the first element of the collection as the accumulator (called sum in the following example).
For example, let's do a sum of integers again:
listOf(1, 2, 3).reduce { sum, element -> sum + element }
The first call to the lambda here will be with parameters 1 and 2.
You can use reduce when your operation does not depend on any values other than those in the collection you're applying it to.
The major functional difference I would call out (which is mentioned in the comments on the other answer, but may be hard to understand) is that reduce will throw an exception if performed on an empty collection.
listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.
This is because .reduce doesn't know what value to return in the event of "no data".
Contrast this with .fold, which requires you to provide a "starting value", which will be the default value in the event of an empty collection:
val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)
So, even if you don't want to aggregate your collection down to a single element of a different (non-related) type (which only .fold will let you do), if your starting collection may be empty then you must either check your collection size first and then .reduce, or just use .fold
val collection: List<Int> = // collection of unknown size
val result1 = if (collection.isEmpty()) 0
else collection.reduce { x, y -> x + y }
val result2 = collection.fold(0) { x, y -> x + y }
assertEquals(result1, result2)
Another difference that none of the other answers mentioned is the following:
The result of a reduce operation will always be of the same type (or a super type) as the data that is being reduced.
We can see that from the definition of the reduce method:
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}
On the other hand, the result of a fold operation can be anything, because there are no restrictions when it comes to setting up the initial value.
So, for example, let us say that we have a string that contains letters and digits. We want to calculate the sum of all the digits.
We can easily do that with fold:
val string = "1a2b3"
val result: Int = string.fold(0, { currentSum: Int, char: Char ->
if (char.isDigit())
currentSum + Character.getNumericValue(char)
else currentSum
})
//result is equal to 6
reduce - The reduce() method transforms a given collection into a single result.
val numbers: List<Int> = listOf(1, 2, 3)
val sum: Int = numbers.reduce { acc, next -> acc + next }
//sum is 6 now.
fold - What would happen in the previous case of an empty list? Actually, there’s no right value to return, so reduce() throws a RuntimeException
In this case, fold is a handy tool. You can put an initial value by it -
val sum: Int = numbers.fold(0, { acc, next -> acc + next })
Here, we’ve provided initial value. In contrast, to reduce(), if the collection is empty, the initial value will be returned which will prevent you from the RuntimeException.
Simple Answer
Result of both reduce and fold is "a list of items will be transformed into a single item".
In case of fold,we provide 1 extra parameter apart from list but in case of reduce,only items in list will be considered.
Fold
listOf("AC","Fridge").fold("stabilizer") { freeGift, itemBought -> freeGift + itemBought }
//output: stabilizerACFridge
In above case,think as AC,fridge bought from store & they give stabilizer as gift(this will be the parameter passed in the fold).so,you get all 3 items together.Please note that freeGift will be available only once i.e for the first iteration.
Reduce
In case of reduce,we get items in list as parameters and can perform required transformations on it.
listOf("AC","Fridge").reduce { itemBought1, itemBought2 -> itemBought1 + itemBought2 }
//output: ACFridge
The difference between the two functions is that fold() takes an initial value and uses it as the accumulated value on the first step, whereas the first step of reduce() uses the first and the second elements as operation arguments on the first step.