how to change nested if-else to when statements using Kotlin? - kotlin

I have the following block of code and want to reduce it using Kotlin. How can I do that?
if (name == nameArray[0]) {
// The statement
} else if(name == nameArray[1]) {
// The statement
} else if(name == nameArray[2]) {
// The statement
} else if(name == nameArray[3]) {
// The statement
} else {
// The statement
}

If the array is small and you want to map an action to each index:
You could use indexOfFirst to determine the smallest index which meats your condition. Then you can use a when statement to decide what to do.
when(nameArray.indexOfFirst{ it == name }) {
0 -> // do something
1 -> // do something else
//...
else -> // do something different
}
In case you might want to do the same thing for multiple indices you can use comma separated values. In case the indices are consecutive, you can use ranges:
when(nameArray.indexOfFirst{ it == name }) {
0 -> // do something
1, 2 -> // do the same thing for 1 and 2
in 3..6 -> // do the same thing for 3, 4, 5 and 6
//...
else -> // do something different
}
In order to use this syntax it is a good idea to do index retrieval (like shown) first.
If the array is big and you really only want to check for specific elements:
when(name) {
nameArray[0] -> // do something
nameArray[1] -> // do something
nameArray[2] -> // do something
nameArray[3] -> // do something
else -> // other action
}

You can use when to simplify it as below
when(name) {
nameArray[0] -> //statement
nameArray[1] -> //statement
nameArray[2] -> //statement
nameArray[3] -> //statement
else -> //statement
}
Alternatively, if you can use an enum instead of the nameArray as below
enum class Names {
NAME_1, NAME_2, NAME_3
}
And have name of the Names enum type, you can then use the when clause as below, which is a cleaner way and is more readable
when(name) {
Names.NAME_1 -> //statement
Names.NAME_2 -> //statement
Names.NAME_3 -> //statement
}

You can use a better and more powerful Kotlin construct, when.
It works similarly to switch-case constructs but you can use expression too, check here for more.
Specific to your example you may write:
when (name) {
nameArray[0] -> {
//The statement
}
nameArray[1] -> {
//The statement
}
nameArray[2] -> {
//The statement
}
nameArray[3] -> {
//The statement
}
else -> {
//Executes when conditions are not met
}
}

Maybe I'm misinterpreting what you want to do, but in my opinion a when statement is over-complicating this. In your original code you just want to determine if the array contains the name value in any of the indices from 0 to 3 and respond accordingly.
if ((nameArray.indexOfFirst(name::equals) in 0..3) {
// The statement
} else {
// The else branch
}

Shorter way: Iterate the array instead of if-else every possible index of the array:
fun main(args: Array<String>) {
val nameArray: Array<String> = arrayOf("naam", "nombre", "name", "Name")
val name: String = "name";
for (i in 0..(nameArray.size - 1)) {
if (name == nameArray[i]) {
println("The statement should be executed on index " + i)
}
}
}
Output:
The statement should be executed on index 2

Related

In Kotlin, how can I test and use a value without computing it twice?

Every so often, I find myself wanting to compute a value for some sort of filter operation, but then wanting to use that value when it's already disappeared into the condition-checking thing.
For instance:
val found = list.firstOrNull { slowConversion(it).isWanted() }
if (found != null) {
something(found, slowConversion(found))
}
or
when {
other_conditions -> other_actions
list.any { it.contains(regex1) } -> something(list.firstOrNull { it.contains(regex1) } ?: "!!??")
}
For the slowConversion() I can work with a sequence mapped to pairs, although the terms first and second kinda confuse things a bit...
val pair = list.asSequence().map { it to slowConversion(it) }.firstOrNull { it.second.isWanted() }
if ( pair != null ) {
something(pair.first, pair.second)
}
or if I only want the conversion,
val converted = list.firstNotNullOfOrNull { slowConversion(it).takeIf { it.isWanted() } }
but the best I can come up with to avoid the when duplication involves moving the action part into the condition part!
fun case(s: List<String>, r: Regex) {
val match = s.firstOrNull { it.contains(r) }?.also { something(it) }
return match != null
}
when {
other_conditions -> other_actions
case(list, regex1) -> true
}
At this point, it seems I should just have a stack of function calls linked together with ||
other_things || case(list, regex1) || case(list, regex2) || catchAll(list)
Is there something better or more concise for either of these?
You can write your first example like this:
for(element in list) {
val result = slowConversion(element)
if(result.isWanted()) {
something(element, result)
break
}
}
This might not look very Kotlin-ish, but I think it's pretty straightforward & easy to understand.
For your second example, you can use the find function:
when {
other_conditions -> other_actions
else -> list.find { it.contains(regex1) }?.let(::something)
}
If you have multiple regexes, just iterate over them,
val regexes = listOf(regex1, regex2, ...)
for(regex in regexes) {
val element = list.find { it.contains(regex1) } ?: continue
something(element)
break
}

Find-first-and-transform for Sequence in Kotlin

I often stumble upon this problem but don't see a common implementation: how do I idiomatically (functionally) find an element, stop search after the match, and also return a different type (i.e. map whatever matched to another type)?
I've been able to do a workaround with
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? =
fold(AtomicReference<T>()) { ref, from ->
if (ref.get() != null) return#fold ref
ref.set(block(from))
ref
}.get()
fun main() {
Files.list(someDir).asSequence().map { it.toFile() }.mapFirst { file ->
file.useLines { lines ->
lines.mapFirst { line ->
if (line == "123") line.toInt() else null
}
}
}?.let { num ->
println("num is $num") // will print 123 as an Int
} ?: println("not a single file had a line eq to '123'")
}
But that doesn't stop on the match (when block() returns non-null) and goes to consume all files and all their lines.
A simple for loop is enough to implement mapFirst:
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? {
for (e in this) {
block(e)?.let { return it }
}
return null
}
If you need a solution without introducing your own extensions (though there's nothing wrong with it), you can use mapNotNull + firstOrNull combination:
files.asSequence()
.mapNotNull { /* read the first line and return not null if it's ok */ }
.firstOrNull()
I would not map the values you discard then, instead do it like this:
sequenceOf(1, 2, 3)
.firstOrNull() { it == 2 }
?.let { it * 2 } ?: 6
First you find the value that matches your condition, then you transform it too whatever you want. In case you don't find a matching element, you assign a default value (in this case 6).

How to implement switch-case statement in Kotlin

How to implement equivalent of following Java switch statement code in Kotlin?
switch (5) {
case 1:
// Do code
break;
case 2:
// Do code
break;
case 3:
// Do code
break;
}
You could do like this:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
extracted from official help
switch in Java is effectively when in Kotlin. The syntax, however, is different.
when(field){
condition -> println("Single call");
conditionalCall(field) -> {
print("Blocks");
println(" take multiple lines");
}
else -> {
println("default");
}
}
Here's an example of different uses:
// This is used in the example; this could obviously be any enum.
enum class SomeEnum{
A, B, C
}
fun something(x: String, y: Int, z: SomeEnum) : Int{
when(x){
"something" -> {
println("You get the idea")
}
else -> {
println("`else` in Kotlin`when` blocks are `default` in Java `switch` blocks")
}
}
when(y){
1 -> println("This works with pretty much anything too")
2 -> println("When blocks don't technically need the variable either.")
}
when {
x.equals("something", true) -> println("These can also be used as shorter if-statements")
x.equals("else", true) -> println("These call `equals` by default")
}
println("And, like with other blocks, you can add `return` in front to make it return values when conditions are met. ")
return when(z){
SomeEnum.A -> 0
SomeEnum.B -> 1
SomeEnum.C -> 2
}
}
Most of these compile to switch, except when { ... }, which compiles to a series of if-statements.
But for most uses, if you use when(field), it compiles to a switch(field).
However, I do want to point out that switch(5) with a bunch of branches is just a waste of time. 5 is always 5. If you use switch, or if-statements, or any other logical operator for that matter, you should use a variable. I'm not sure if the code is just a random example or if that's actual code. I'm pointing this out in case it's the latter.
The switch case is very flexible in kotlin
when(x){
2 -> println("This is 2")
3,4,5,6,7,8 -> println("When x is any number from 3,4,5,6,7,8")
in 9..15 -> println("When x is something from 9 to 15")
//if you want to perform some action
in 20..25 -> {
val action = "Perform some action"
println(action)
}
else -> println("When x does not belong to any of the above case")
}
When Expression
when replaces the switch operator of C-like languages. In the simplest form it looks like this
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
when matches its argument against all branches sequentially until some branch condition is satisfied. when can be used either as an expression or as a statement. If it is used as an expression, the value of the satisfied branch becomes the value of the overall expression. If it is used as a statement, the values of individual branches are ignored. (Just like with if, each branch can be a block, and its value is the value of the last expression in the block.)
From https://kotlinlang.org/docs/reference/control-flow.html#when-expression
when defines a conditional expression with multiple branches. It is similar to the switch statement in C-like languages. Its simple form looks like this.
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
when matches its argument against all branches sequentially until some branch condition is satisfied.
when can be used either as an expression or as a statement. If it is used as an expression, the value of the first matching branch becomes the value of the overall expression. If it is used as a statement, the values of individual branches are ignored. Just like with if, each branch can be a block, and its value is the value of the last expression in the block.
import java.util.*
fun main(args: Array<String>){
println("Hello World");
println("Calculator App");
val scan=Scanner(System.`in`);
println("""
please choose Your Selection to perform
press 1 for addition
press 2 for substraction
press 3 for multipication
press 4 for divider
press 5 for divisible
""");
val opt:Int=scan.nextInt();
println("Enter first Value");
val v1=scan.nextInt();
println("Enter Second Value");
val v2=scan.nextInt();
when(opt){
1->{
println(sum(v1,v2));
}
2->{
println(sub(v1,v2));
}
3->{
println(mul(v1,v2));
}
4->{
println(quotient(v1,v2));
}
5->{
println(reminder(v1,v2));
}
else->{
println("Wrong Input");
}
}
}
fun sum(n1:Int,n2:Int):Int{
return n1+n2;
}
fun sub(n1:Int, n2:Int):Int{
return n1-n2;
}
fun mul(n1:Int ,n2:Int):Int{
return n1*n2;
}
fun quotient(n1:Int, n2:Int):Int{
return n1/n2;
}
fun reminder(n1:Int, n2:Int):Int{
return n1%n2;
}
Just use the when keyword. If you want to make a loop, you can do like this:
var option = ""
var num = ""
while(option != "3") {
println("Choose one of the options below:\n" +
"1 - Hello World\n" +
"2 - Your number\n" +
"3 - Exit")
option = readLine().toString()
// equivalent to switch case in Java //
when (option) {
"1" -> {
println("Hello World!\n")
}
"2" -> {
println("Enter a number: ")
num = readLine().toString()
println("Your number is: " + num + "\n")
}
"3" -> {
println("\nClosing program...")
}
else -> {
println("\nInvalid option!\n")
}
}
}
val operator = '+'
val a = 6
val b = 8
val res = when (operator) {
'+' -> a + b
'-' -> a - b
'*' -> a * b
'/' -> a / b
else -> 0
}
println(res);
We use the following code for common conditions
val operator = '+'
val a = 6
val b = 8
val res = when (operator) {
'+',
'-' -> a - b
'*',
'/' -> a / b
else -> 0
}
println(res);
In kotlin, their is no switch-case statement. But we have when expression similar to switch. Just like if-else or switch, first all conditions are checked, if none matches then else code evaluated.
when (n) {
1 -> {
print("First")
// run your code
}
2 -> print("Second")
3, 4 -> print("Third or Forth") // check multiple conditions for same code
in 1..100 -> print("Number is in the range")
else -> {
print("Undefined")
}
}
There is no need of any break as of switch case.
Here is an example to know Using “when” with arbitrary objects,
VehicleParts is a enum class with four types.
mix is a method which accepts two types of VehicleParts class.
setOf(p1, p2) - Expression can yield any object
setOf is a kotlin standard library function that creates Set containing the objects.
A set is a collection for which the order of items does not matter.
Kotlin is allowed to combine different types to get mutiple values.
When I pass VehicleParts.TWO and VehicleParts.WHEEL, I get "Bicycle".
When I pass VehicleParts.FOUR and VehicleParts.WHEEL, I get "Car".
Sample Code,
enum class VehicleParts {
TWO, WHEEL, FOUR, MULTI
}
fun mix(p1: VehicleParts, p2: VehicleParts) =
when (setOf(p1, p2)) {
setOf(VehicleParts.TWO, VehicleParts.WHEEL) -> "Bicycle"
setOf(VehicleParts.FOUR, VehicleParts.WHEEL) -> "Car"
setOf(VehicleParts.MULTI, VehicleParts.WHEEL) -> "Train"
else -> throw Exception("Dirty Parts")
}
println(mix(VehicleParts.TWO,VehicleParts.WHEEL))
If You want to print or open multiple Activities using switch case (When) in Kotlin then use this code.. Thank you..
var dataMap: Map<String?, String?> = HashMap()
var noteType: String? = ""
when (noteType) {
"BIGTEXT" -> NEW_PAGE(dataMap)
"NORMAL" -> NORMAL_PAGE(dataMap)
"ABOUT"->ABOUT_PAGE((dataMap))
}

How to use Kotlin fold function to convert an array into a map?

I am trying to convert an Array via fold into an indexed Map. Somehow IntelliJ flags that when I return the accumulator that it expects Unit. When I remove the return it complains that I require the datatype I originally wanted to return.
The code is as follows (Item is just a data class)
constructor(vararg items: Item){
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return acc
})
}
Its a bit late here so I probably miss something very obvious. Any help would be very appreciated.
Thanks
Use the qualified return#fold operator instead of return. In Kotlin, return without a qualifier means 'return from the innermost fun (ignoring lambdas)'.
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
return#fold acc
})
See Whats does “return#” mean?, Return at Labels in the language reference.
Or just use the result expression, omitting return:
val itemMap = items.fold(mutableMapOf<Int, MutableList<Item>>(), { acc, item ->
if (acc.containsKey(item.state)) {
acc[item.state]?.add(item)
} else {
acc.put(item.state, mutableListOf(item))
}
acc
})
Basically, this kind of fold is implemented in the standard library: see .groupBy { ... }.

Using condition to select the sorting property in Kotlin

I am using sortedBy() to perform sorting on the collection of objects.
Since the order may change depending on the user choice, I've ended up with the following code
val sortedList = if (sortingOrder == WordSortingOrder.BY_ALPHA) {
list.sortedBy { it.word.value }
} else {
list.sortedBy { it.createdAt }
}
Then I perform further actions on the sorted collection.
I realize that sortedBy() method expects a property to be returned.
I wonder if there is a way to embed the sorting condition in one chain of collection methods.
If your properties are of different types you won't be able to select one of them based on some condition as a result for sortedBy, as their common supertype would be inferred as Any and it is not a subtype of Comparable<R> as sortedBy expects.
Instead you can utilize sortedWith method, which takes a Comparator, and provide a comparator depending on the condition:
list.sortedWith(
if (sortingOrder == WordSortingOrder.BY_ALPHA)
compareBy { it.word.value }
else
compareBy { it.createdAt }
)
Comparators for different properties are created here with the kotlin.comparisons.compareBy function.
You can then extract the logic which selects comparator based on sorting order to a function:
list.sortedWith(comparatorFor(sortingOrder))
fun comparatorFor(sortingOrder: WordSortingOrder): Comparator<MyType> = ...
The sortedBy expects any function of type (T) -> R as its parameter. A property is a corner case of that.
Which means you can do this:
val sortedList = list
.sortedBy { if (sortingOrder == WordSortingOrder.BY_ALPHA) it.word.value else it.createdAt}
Or, if you need something more OOP-ish:
enum class WordSortingOrder(val transform: (MyObject) -> Int) {
BY_ALPHA({it.word.value}),
BY_ALPHA_REVERSED({-1 * it.word.value}),
DEFAULT({it.createdAt})
}
val sortedList = list.sortedBy { sortingOrder.transform(it)}
You can do something like:
list.sortedBy { item ->
when(sortingOrder) {
WordSortingOrder.BY_ALPHA -> item.word.value
else -> item.createdAt
}
}
You can make the lambda argument passed to sortedBy conditional:
list.sortedBy(if (sortingOrder == WordSortingOrder.BY_ALPHA) {
{ it: MyType -> it.word.value }
} else {
{ it: MyType -> it.createdAt }
})
You may find using when instead of if more readable in this scenario:
list.sortedBy(when (sortingOrder) {
WordSortingOrder.BY_ALPHA -> { it: MyType -> it.word.value }
else -> { it: MyType -> it.createdAt }
})
If your selectors have different return types then you can simply wrap your existing code within list.let { list -> ... } or use run:
list.run {
if (sortingOrder == WordSortingOrder.BY_ALPHA) {
sortedBy { it.word.value }
} else {
sortedBy { it.createdAt }
}
}
You can then continue chainging calls after the let/run.