How to simplify multiple equals checks in if condition? - kotlin

How can I do this easier with Kotlin?
if (translation.equals(TRANSLATION_X) ||
translation.equals(TRANSLATION_Y) ||
translation.equals(TRANSLATION_Z)
) {
return
} else {
translation = TRANSLATION_X
}

First, you can use the structural equality operator ==, which is translated to the .equals(...) calls automatically: translation == TRANSLATION_X instead of translation.equals(TRANSLATION_X).
Then, you can use the when statement:
when (translation) {
TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z -> return
else -> translation = TRANSLATION_X
}

Another alternative that may be more efficient than a when expression is to use a Set:
val options = setOf(TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z)
if (translation in options) return
else translation = TRANSLATION_X

A when statement seems appropriated in this situation :
val translation = when( translation ) {
TRANSLATION_X -> translation
TRANSLATION_Y -> translation
TRANSLATION_Z -> translation
else TRANSLATION_X
}
I think you can also group the three similar cases in one sentence like this :
val translation = when( translation ) {
TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z -> translation
else TRANSLATION_X
}

Related

How do I get rid of Boolean method is always inverted warning?

fun findError(puzzle: Array<IntArray>): Boolean {
for (z in 0..8) {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
}
}
return true
}
This function works as desired but the Intellij IDE gives this warning. "Boolean method 'findError' is always inverted". I kind of understand what it means and I know I could suppress it.
I can't figure out how to rewrite the code block to satisfy the error and not change the functionality. Should I just suppress it or is there a more proper way to express this? I'm a beginner that is learning.
public final data class Blocks public constructor(blockNums: kotlin.collections.MutableList<kotlin.Int>, blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int>, xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>>) {
public final val blockNums: kotlin.collections.MutableList<kotlin.Int> /* compiled code */
public final val blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int> /* compiled code */
public final var xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>> /* compiled code */
public final operator fun component1(): kotlin.collections.MutableList<kotlin.Int> { /* compiled code */ }
public final operator fun component2(): kotlin.collections.MutableSet<kotlin.Int> { /* compiled code */ }
public final operator fun component3(): kotlin.collections.List<kotlin.collections.List<kotlin.Int>> { /* compiled code */ }
}
I found a way to rewrite the function not using the '!' symbol. This should satisfy the intention of the warning to use positives instead of negatives. It still gives the warning. I think Bas Leijdekkers comment about the inspection may be correct.
fun findError(puzzle: Array<IntArray>): Boolean {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
var counts = 0
for (z in 0..8) {
blockNums.clear()
xNums.clear()
yNums.clear()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() == blockNums.toSet().count() &&
yNums.count() == yNums.toSet().count() &&
xNums.count() == xNums.toSet().count()) {
counts++
}
}
}
return counts == 81
}
Which line is the IntelliJ warning you about, is it this statement?
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
if so, it is because this is complex and likely difficult for someone other than you to understand. Here's two ideas about how you might make reduce the complexity:
(1)
if (blockNums.size != blockNums.toSet().size) return false
if (yNums.size != yNums.toSet().size) return false
if (xNums.size != xNums.toSet().size) return false
or (2)
val blocksDiffer = (blockNums.size != blockNums.toSet().size)
val yDiffer = (yNums.size != yNums.toSet().size)
val xDiffer = (xNums.size != xNums.toSet().size)
if(blocksDiffer || yDiffer || xDiffer) return false
(There is a small performance penalty with (2) since the program has to compute all 3 evaluations.
I also changed count() to size which are equivalent)
I believe this is IntelliJ trying to warn you that a boolean method result is always inverted after calling. In other words, you only ever use !findError() in your code.
This is an indication that the code could be made more readable by using positive language. JetBrain's justification for this appears to be based on Robert Martin's book Clean Code:
“Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.”
You can use Refactor -> Invert Boolean... to perform this automatically.

Null check with ?. vs ?: fails in kotlin

I have following statement in my code:
safeOrderResult.accomplished?.let{ safeAccomplished->
//Do something with safeAccomplished when accomplished <> null
Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}")
}?:let{
//Do something when accomplished == null
Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}
Here my code does something strange:
On a Samsung TAB A (i think not significant) it works as expected.
On a Samsung S9 it calls both let sections.
Snippet from Logcat Samsung S9 (android 10)
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 408
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: handleDate = null <- inside above let: ok
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId?:let{} *null*
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: flagged = false or null
TabA: android 10
2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 427
2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:21:16.678 2468-2468/no.norva24.mslam I/ViewHolder: bind: handleDate = null <-- inside above let
2021-05-06 14:21:16.685 2468-2468/no.norva24.mslam I/ViewHolder: bind: flagged = false or null
The key point is, how can a value both be null and contain a value?, or can kotlin "change" to null and kick in in the second "null" let, if value has changed in the first first let (which I didn't do)
I am Using kotlin 1.5.0
EDIT 2021.05.06 18:55 GMT+2
I am not sure, but I might have learned something here today: ;)
safeOrderResult.accomplished?.let{ safeAccomplished->
//Do something with safeAccomplished when accomplished <> null
/*Here I have preserved my value in safeAccomplished
And actually returning a value below (a Unit()) from Log.i ?*/
Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}")
}?:let{
//Do something when accomplished == null
/* But why did the code kick in here ?
After it was inside the let above ? I thought the '?:let' was
continuing if the '?.let' didn't kick in.
*/
Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}
/*
Below is the actual code which had the trouble (the code isn't finished therefore the "preserved" `let` values isn't used)
*/
safeOrderResult.accomplishedId?.let {
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.VISIBLE
listItemOrderListMaterialTextViewOrderStatus.text =
context.resources.getStringArray(
R.array.basic_register_accomplish_status_names)[1]
listItemOrderListMaterialTextViewDate.text =
dateStringSplitSpace(safeOrderResult.registeredDate)
Log.i(TAG, "bind: handleDate = ${safeOrderResult.handleDate}")
listItemOrderListMaterialTextViewReason.text =
if(safeOrderResult.handleDate.isNullOrEmpty())
"Still possible to update"
else
"Assignment locked on ${dateStringSplitSpace(safeOrderResult.handleDate)}"
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
if(safeOrderResult.handleDate.isNullOrEmpty())
R.color.list_item_register_field_accomplished_background
else
R.color.list_item_register_field_accomplished_locked_background
)
)
}
listItemOrderListLinearLayoutCompatStatusMore?.apply {
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
if(safeOrderResult.handleDate.isNullOrEmpty())
R.color.list_item_register_field_accomplished_background
else
R.color.list_item_register_field_accomplished_locked_background
)
)
}
}?:let {
safeOrderResult.passedId?.let { safePassedId->
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.VISIBLE
listItemOrderListMaterialTextViewOrderStatus.text =
context.resources.getStringArray(
R.array.basic_register_accomplish_status_names
)[2]
listItemOrderListMaterialTextViewDate.text =
dateStringSplitSpace(safeOrderResult.registeredDate)
listItemOrderListMaterialTextViewReason.text =
safeOrderResult.passedReason
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.list_item_register_field_passed_background,
)
)
}
}?:let {
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.GONE
}
}
}
** ADDENDUM 2020.05.06 19:30 GMT+2 **
In playground I got trouble with this:
/**
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
class A {
fun f() {
let { println(it) }
}
}
data class DataClass(
var value1:String?=null,
var value2:String?=null
)
fun main() {
A().f()
var myData = DataClass()
myData.value1 = "1"
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}?:let{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}?:let{
println("value2==null !!")
}
}
Where it kicked on the ?:let's above. This was ok in kotin v.1.5.0 at least...
ADDENDUM 2: 2020.05.06 19:40 GMT+2
So... dataClass.value?:let{ } isn't allowed ? in a 'standard' kotlin scenario to check for null existence ?, but still 'valid' in AS2020.3.1.15 w/kotlin 1.5.0 ...
ADDENDUM 3: 2020.05.06 19:55 GMT+2
When using another approach (omitting let keyword in ?:let{ I got this answer to the based on the playground code above:
Here I expected also the value2 to show up with value2==null !! but it didn`t...
Here's the playground code now:
/**
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
class A {
fun f() {
let { println(it) }
}
}
data class DataClass(
var value1:String?=null,
var value2:String?=null
)
fun main() {
A().f()
var myData = DataClass()
myData.value1 = "1"
/*
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}?:let{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}?:let{
println("value2==null !!")
}
*/
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}
myData.value1?:{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}
myData.value2?:{
println("value2==null !!")
}
}
...still a little confused ...
The let function can indeed change your target to null. It changes the target to whatever it returns. A lambda implicitly returns the result of its last expression. Your code above has a Log.i() call as its last expression, so it returns Unit, so the second let function should never run if the first one does. Is it possible you've snipped off some code at the end of your first let lambda that could possibly return a null value?
A quick fix for the above problem would be to swap let for also, because also always returns its receiver.
I think most experienced Kotlin users will advise you not to chain scope function calls like this because it makes the code hard to follow and it is easy to introduce subtle bugs. You can write a more robust version like this:
val accomplished = safeOrderResult.accomplished
if (accomplished != null) {
//Do something with smart-cast non-nullable accomplished
} else {
//Do something when accomplished == null
}
At a guess, the first one is returning null at the end, which means the value produced by that whole expression is null, so the stuff after the ?: is triggered (since that's an "if the left side evaluates to null" condition).
Why that would only happen on some Samsung models - who knows, they have a history of messing with things in the Android library! I'd check exactly what's going on in the block and what it might evaluate to. You might need to return Unit at the end, or use a function like apply that returns the receiver instead of the result of the lambda.
This is why the if/else is a better fit - you have a condition at the start, and you decide whether to do one thing or another, exclusively. let produces a value, and it's often used to propagate a value down a chain, and return a result. ?: is a final default value, for if that result turns out to be null.
It's absolutely possible to run the let block and the code after the ?:, and sometimes that a thing you want to do. As a construction it's often used for returning a default value. So if/else is a little more explicit about what you're doing and how it's meant to work, and it helps avoid surprise bugs like this one!
If you don't want to bind accomplished to a variable as in #Tenfour04's answer, I'd write it as
safeOrderResult.accomplished.let {
if (it != null) {
// safeOrderResult.accomplished was not null, use it
} else {
// safeOrderResult.accomplished was null
}
}
or
safeOrderResult.accomplished.let { accomplished ->
if (accomplished != null) {
// safeOrderResult.accomplished was not null, use accomplished
} else {
// safeOrderResult.accomplished was null
}
}
Note .let and not ?.let. But pick on readability/convenience. I definitely wouldn't use
value?.let{ safeValue-> /*dowork*/}; value?:let{/*do null work*/}
as you suggest in another comment.
You can do an if-null-else with ?.let but it's not very readable in my opinion
var s: String? = "Str"
s?.let { println("A ok") } ?: run { println("A null") }
s = null
s?.let { println("B ok") } ?: run { println("B null") }
A ok
B null
It is also possible to introduce subtle bugs like this:
var s: String? = "Str"
s?.let { println("A ok"); null } ?: run { println("A null") }
A ok
A null
This is why you should use an if-else if you both need the non-null and null case. (?. is intended for the case where only the non-null case makes sense):
if (s == null) println("A null") else println("A ok")
if (s == null) {
println("A null")
} else {
println("A ok")
}
Thanx for many good answers above, and you all are right...
I landed on following solution for my problem, but still not quite happy though:
I use .apply to remove some value. overhead,
safeOrderResult.apply{
if(accomplished!=null){
//Do something with accomplished since accomplished <> null
Log.i(TAG,"bind: accomplished != null")
}else{
//Do something when accomplished == null
Log.i(TAG,"bind: accomplished == null" )
}
}
I mark accepted for #Alexey Romanov suggestion which is quite reasonable.

How to get the certain element in a Set on a .forEach iteration in Kotlin

What is the expression after the "pairedDevices" in order to get this certain element that satisfies the if condition?
//creating a SET of paired devices
val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
//for each paired device
pairedDevices?.forEach { device ->
if (device.name == "HC-05") {
val hc05 = pairedDevices
}
}
I think you're looking for
hc05 = pairedDevices?.first { it.name == "HC-05" }

Is there a way to merge filter and map into single operation in Kotlin?

The below code will look for "=" and then split them. If there's no "=", filter them away first
myPairStr.asSequence()
.filter { it.contains("=") }
.map { it.split("=") }
However seeing that we have both
.filter { it.contains("=") }
.map { it.split("=") }
Wonder if there's a single operation that could combine the operation instead of doing it separately?
You can use mapNotNull instead of map.
myPairStr.asSequence().mapNotNull { it.split("=").takeIf { it.size >= 2 } }
The takeIf function will return null if the size of the list returned by split method is 1 i.e. if = is not present in the string. And mapNotNull will take only non null values and put them in the list(which is finally returned).
In your case, this solution will work. In other scenarios, the implementation(to merge filter & map) may be different.
I see your point and under the hood split is also doing an indexOf-check to get the appropriate parts.
I do not know of any such function supporting both operations in a single one, even though such a function would basically just be similar to what we have already for the private fun split-implementation.
So if you really want both in one step (and require that functionality more often), you may want to implement your own splitOrNull-function, basically copying the current (private) split-implementation and adapting mainly 3 parts of it (the return type List<String>?, a condition if indexOf delivers a -1, we just return null; and some default values to make it easily usable (ignoreCase=false, limit=0); marked the changes with // added or // changed):
fun CharSequence.splitOrNull(delimiter: String, ignoreCase: Boolean = false, limit: Int = 0): List<String>? { // changed
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
var currentOffset = 0
var nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
if (nextIndex == -1 || limit == 1) {
if (currentOffset == 0 && nextIndex == -1) // added
return null // added
return listOf(this.toString())
}
val isLimited = limit > 0
val result = ArrayList<String>(if (isLimited) limit.coerceAtMost(10) else 10)
do {
result.add(substring(currentOffset, nextIndex))
currentOffset = nextIndex + delimiter.length
// Do not search for next occurrence if we're reaching limit
if (isLimited && result.size == limit - 1) break
nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
} while (nextIndex != -1)
result.add(substring(currentOffset, length))
return result
}
Having such a function in place you can then summarize both, the contains/indexOf and the split, into one call:
myPairStr.asSequence()
.mapNotNull {
it.splitOrNull("=") // or: it.splitOrNull("=", limit = 2)
}
Otherwise your current approach is already good enough. A variation of it would just be to check the size of the split after splitting it (basically removing the need to write contains('=') and just checking the expected size, e.g.:
myPairStr.asSequence()
.map { it.split('=') }
.filter { it.size > 1 }
If you want to split a $key=$value-formats, where value actually could contain additional =, you may want to use the following instead:
myPairStr.asSequence()
.map { it.split('=', limit = 2) }
.filter { it.size > 1 }
// .associate { (key, value) -> key to value }

Filtering data out of a collection with specific string

I would like to receive only data which does not contain some specific strings.
What I have is this code:
#GET
#Path("getParticipants")
#Produces(MediaType.APPLICATION_JSON)
fun getParticipants(): Map<String, List<String>> {
val (nodeInfo, nodeUpdates) = rpcOps.networkMapFeed()
nodeUpdates.notUsed()
return mapOf("getParticipants" to nodeInfo
.map { it.legalIdentities.first().toString() }
.filter { it != myLegalName && it !in NOTARY_NAMES })
}
While running this code above I recive this:
0 "C=GB,L=London,O=UserA"
1 "C=GB,L=London,O=Controller"
2 "C=US,L=New York,O=UserB"
myLegalName is = "UserA"
NOTARY_NAMES is = "Controller"
What I would like to achive is, that the getParticipants retrieves me only the rows which are NOT CONTAINING UserA ("myLegalName") and NOT CONTAINING Controller ("NOTARY_NAMES"), so in this case only "C=US,L=New York,O=UserB".
Based upon your stated output, your predicate after substituting for your result values looks like this...
"C=GB,L=London,O=UserA" != "UserA" && "C=GB,L=London,O=UserA" !in "Controller"
// or, after using information from your comments
"C=GB,L=London,O=UserA" != "UserA" && "C=GB,L=London,O=UserA" !in listOf("Controller","NetworkSErvice")
... and of course neither filters out what you want.
If some other field besides legalIdentities.first() contains the information you want, then filter on that (and call filter before map)...
.filter { it.someOtherField() != myLegalName &&
it.someOtherField() !in NOTARY_NAMES } //then...
.map { it.legalIdentities.first().toString() }
Otherwise, parse .legalIdentities.first() to get the "O" member of the string and filter on that.