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 }
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 have a simple if () condition which needs to check if value is either 1 or 2 (for example). Let's say the value we are comparing against is not 'simple':
if(it.first().property.value == 1 || it.first().property.value == 2) {
// Do stuff
}
Is there a better way to perform this check (without typing the entire expression to get the actual value twice)? The only thing that comes to mind is
if(listOf(1, 2).contains(it.first().property.value)) {
// Do stuff
}
But I'm afraid it's more memory consuming since it has additional list introduced.
Your last suggestion is a good one in general, though it's usually better to use a predefined list (and the in operator):
// At the top level, or in a (companion) object:
val acceptableValues = listOf(1, 2)
// Then in the relevant part of the code:
if (it.first().property.value in acceptableValues)
// Do stuff
That only allocates the list once, and performance is about as good as any other option. It's also very readable, and general.
(If the list doesn't naturally fit into a named property, you'd have to judge how often it might be needed, in order to trade a minor performance benefit against the conciseness of putting it directly in the condition.)
In fact, because you're looking for consecutive integers, there's a more concise option for this particular test:
if (it.first().property.value in 1..2)
// Do stuff
That would work whenever the acceptable values form an (uninterrupted) range.
Alternatively, if you're always checking against exactly two values, you could write a simple extension function:
fun <T> T.isEither(a: T, b: T) = this == a || this == b
(You could write a more general one using a vararg param, but that would create an array each time — very similar to the in listOf() case we started with.)
You can decide it using a when expression like in this example:
fun main() {
val number = 22
when (number) {
1, 2 -> println("${number} is 1 or 2")
in 10..20 -> println("${number} is between 10 and 20 (inclusively)")
else -> println("${number} is either negative, equals 0, 3, 4, 5, 6, 7, 8, 9, 21 or any number above")
}
}
The output here is
22 is either negative, equals 0, 3, 4, 5, 6, 7, 8, 9, 21 or any number above
You could define an extension function on the type of it to make it more readable:
if(it.isOneOrTwo()) {
// Do stuff
}
Not sure what's the type of your it, replace TYPEOFIT accordingly:
private inline fun TYPEOFIT.isOneOrTwo() = first().property.value == 1 || first().property.value == 2
To additionally improve the condition you could leverage when:
private inline fun TYPEOFIT.isOneOrTwo() = when(first().property.value) {
1,2 -> true
else -> false
}
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)
Here's the problem in which I encountered this issue:
The function should compare the value at each index position and score a point if the value for that position is higher. No point if they are the same. Given a = [1, 1, 1] b = [1, 0, 0] output should be [2, 0]
fun compareArrays(a: Array<Int>, b: Array<Int>): Array<Int> {
var aRetVal:Int = 0
var bRetVal:Int = 0
for(i in 0..2){
when {
a[i] > b[i] -> aRetVal + 1 // This does not add 1 to the variable
b[i] > a[i] -> bRetVal++ // This does...
}
}
return arrayOf(aRetVal, bRetVal)
}
The IDE even says that aRetVal is unmodified and should be declared as a val
What others said is true, but in Kotlin there's more. ++ is just syntactic sugar and under the hood it will call inc() on that variable. The same applies to --, which causes dec() to be invoked (see documentation). In other words a++ is equivalent to a.inc() (for Int or other primitive types that gets optimised by the compiler and increment happens without any method call) followed by a reassignment of a to the incremented value.
As a bonus, consider the following code:
fun main() {
var i = 0
val x = when {
i < 5 -> i++
else -> -1
}
println(x) // prints 0
println(i) // prints 1
val y = when {
i < 5 -> ++i
else -> -1
}
println(y) // prints 2
println(i) // prints 2
}
The explanation for that comes from the documentation I linked above:
The compiler performs the following steps for resolution of an operator in the postfix form, e.g. a++:
Store the initial value of a to a temporary storage a0;
Assign the result of a.inc() to a;
Return a0 as a result of the expression.
...
For the prefix forms ++a and --a resolution works the same way, and the effect is:
Assign the result of a.inc() to a;
Return the new value of a as a result of the expression.
Because
variable++ is shortcut for variable = variable + 1 (i.e. with assignment)
and
variable + 1 is "shortcut" for variable + 1 (i.e. without assignment, and actually not a shortcut at all).
That is because what notation a++ does is actually a=a+1, not just a+1. As you can see, a+1 will return a value that is bigger by one than a, but not overwrite a itself.
Hope this helps. Cheers!
The equivalent to a++ is a = a + 1, you have to do a reassignment which the inc operator does as well.
This is not related to Kotlin but a thing you'll find in pretty much any other language
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.