Is it impossible to combine `repeat()` and `while()` to loop 3 times? - kotlin

I am new to Kotlin. How do I combine repeat() and while() to loop? I know how to loop by using following 2 methods:
// Method 1
for (i in 1..3) {
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
if (fortune.contains("Take it easy")) break;
}
// Method 2
var fortune: String = ""
var i: Int = 0 //i = 0
while (!fortune.contains("Take it easy") && i<3 ) {
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
i += 1 // i++
}
But I don't know how to combine repeat() and while() to loop. What I wrote below does NOT exit the loop at the 3rd times but will exit ONLY when the fortune string contains "Take it easy".
What I want is to exit the loop at either the 3rd time of entering your birthday or the fortune string contains "Take it easy". What I wrote below doesn't exit the loop at the 3rd time of entering your birthday. Is it impossible to combine repeat() and while() to loop 3 times?
fun main(args: Array<String>) {
var fortune: String = ""
repeat(3) {
while (!fortune.contains("Take it easy")) {
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
}
}
}
fun getBirthday(): Int {
print("\nEnter your birthday: ")
return readLine()?.toIntOrNull() ?: 1
}
fun getFortune(birthday: Int): String {
val fortunes = listOf("You will have a great day!",
"Things will go well for you today.",
"Enjoy a wonderful day of success.",
"Be humble and all will turn out well.",
"Today is a good day for exercising restraint.",
"Take it easy and enjoy life!",
"Treasure your friends, because they are your greatest fortune.")
val index = when (birthday) {
in 1..7 -> 4
28, 31 -> 2
else -> birthday.rem(fortunes.size)
}
return fortunes[index]
}

It doesn't make sense to combine both to make it loop 3 times. You would use one or the other. When you nest loops like this, it's like multiplying them. The outer repeat happens 3 times, so the inner while loop will loop all the way through from the beginning 3 times. However, in your example, your criterion for exiting the loop will be immediately satisfied the second two times so there will be no output from those subsequent runs.
Basically, if you have a repeating action but you have multiple conditions for when to finish it, the solution is not to add another loop, but instead you must add the additional logic for exiting the loop.
Since repeat is not a keyword, but rather it is a higher-order function, it is more complicated to exit the loop and doing so makes the code hard to read. So I don't recommend using repeat if you have any conditions for exiting the loop early. To exit it early, you have to do something like wrapping it in the run scope function so you can return from that scope function to break out of the loop:
fun main(args: Array<String>) {
var fortune: String = ""
run loop#{
repeat(3) {
if (fortune.contains("Take it easy")) {
return#loop
}
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
}
}
}
Much simpler to use a for loop than using repeat if you want to break out early, as shown in #evadeflow's answer, since you can use the break keyword.

for(i in 0..2){
println(i);
}

I don't entirely understand what you're trying to accomplish, but I think you're saying you want to execute the loop until either:
The fortune string contains "Take it easy", or:
The loop has executed three times
I'm no Kotlin expert, but I don't believe it's possible to use a break statement inside a repeat loop, so you'll need to do something like this instead:
fun main(args: Array<String>) {
var fortune: String
for (i in 1..3) {
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
if (fortune.contains("Take it easy")) {
break
}
}
}
This will execute the loop a maximum of three times, but it may exit earlier if fortune contains "Take it easy".

Related

Slice() nested for loop values i and j Kotlin

I'm wanting to slice a range which I can do in Javascfript but am struggling in kotlin.
my current code is:
internal class blah {
fun longestPalindrome(s: String): String {
var longestP = ""
for (i in 0..s.length) {
for (j in 1..s.length) {
var subS = s.slice(i, j)
if (subS === subS.split("").reversed().joinToString("") && subS.length > longestP.length) {
longestP = subS
}
}
}
return longestP
}
and the error I get is:
Type mismatch.
Required:
IntRange
Found:
Int
Is there a way around this keeping most of the code I have?
As the error message says, slice wants an IntRange, not two Ints. So, pass it a range:
var subS = s.slice(i..j)
By the way, there are some bugs in your code:
You need to iterate up to the length minus 1 since the range starts at 0. But the easier way is to grab the indices range directly: for (i in s.indices)
I assume j should be i or bigger, not 1 or bigger, or you'll be checking some inverted Strings redundantly. It should look like for (j in i until s.length).
You need to use == instead of ===. The second operator is for referential equality, which will always be false for two computed Strings, even if they are identical.
I know this is probably just practice, but even with the above fixes, this code will fail if the String contains any multi-code-unit code points or any grapheme clusters. The proper way to do this would be by turning the String into a list of grapheme clusters and then performing the algorithm, but this is fairly complicated and should probably rely on some String processing code library.
class Solution {
fun longestPalindrome(s: String): String {
var longestPal = ""
for (i in 0 until s.length) {
for (j in i + 1..s.length) {
val substring = s.substring(i, j)
if (substring == substring.reversed() && substring.length > longestPal.length) {
longestPal = substring
}
}
}
return longestPal
}
}
This code is now functioning but unfortunately is not optimized enough to get through all test cases.

Removing a loop to make code run faster (Kotlin) (Big O)

I'm trying a leetcode challenge and am struggling to pass the challenge due to the speed of my code:
class Solution {
fun longestPalindrome(s: String): String {
var longestPal = ""
var substring = ""
for (i in 0..s.length) {
for (j in i + 1..s.length) {
substring = s.substring(i, j)
if (substring == substring.reversed() && substring.length > longestPal.length) {
longestPal = substring
}
}
}
return longestPal
}
}
I'm a newb and not familiar with Big O notation.
I imagine if I could use just one loop I would be able to speed this code up significantly but am not sure how I would go about this.
(Not saying this is the best approach, but that is a start)
Palindromes can only be found between two same letters. So one idea is to go through the string once, and keep track of letter indexes. When you encounter a letter you already saw before, and the difference in indexes is longer than the current longest palindrome, you check for a palindrome:
fun longestPal(s: String): String {
val letters = mutableMapOf<Char, MutableList<Int>>()
var longest = ""
s.indices.forEach { index ->
val indicesForCurrentChar = letters[s[index]] ?: mutableListOf()
for (i in indicesForCurrentChar) {
if ((index - i) < longest.length) break // (1) won't be the longest anyway
val sub = s.substring(i, index + 1)
if (sub == sub.reversed()) longest = sub
}
indicesForCurrentChar.add(index)
letters[s[index]] = indicesForCurrentChar
}
return longest
}
What is costly here is the palindrome check itself (sub == sub.reversed). But the check in (1) should contain it (think of a string which is the repetition of the same letter).
I would be curious to know what other suggest online.
Your code runs in O(n^3) time, a loop within a loop within a loop, because that reversed() call iterates up to the size of the input string. You can look up Manacher's algorithm for an explanation of how to do it in linear time (O(n)), no nested iteration at all.

How do I write this for-loop in functional style?

I have a function with a for-loop:
fun List<Int>.customSum(sumFunction: (Int) -> Boolean): Int {
var sum = 0
for (item in this) {
if (sumFunction(item))
sum += item
}
return sum
}
I want to know how I can write the above in functional style. I know that I have to use this.reduce(), but don't know exactly how to implement it.
return filter(sumFunction).sum()
Should be self-explanatory.
You can’t use reduce because it doesn’t let you reject the first element.
With fold it would be:
return fold(0) { a, b ->
if(sumFunction(b)) a + b else a
}
I can think if two ways to achieve that:
The first one is by using sumOf {...}:
.
fun List<Int>.customSum(sumFunction: (Int) -> Boolean): Int {
return sumOf {
if (sumFunction(it)) it else 0
}
}
The second one is by using filter {...} then sum():
.
fun List<Int>.customSum(sumFunction: (Int) -> Boolean): Int {
return filter(sumFunction).sum()
}
return this.reduce { sum, n -> if (sumFunction(n)) sum + n else 0}
If you really want to use reduce for some reason you can - but you need to add that 0 to the head of the list as your "start state":
fun List<Int>.customSum(sumFunction: (Int) -> Boolean): Int {
val stuff = listOf(0) + this
return stuff.reduce { a, b -> a + if (sumFunction(b)) b else 0 }
}
You have to do that because reduce is really there to combine a bunch of items, which is why for the first iteration you get the first two items in the list. You don't get to handle them separately, which is why you need to throw that 0 in there to get past that first step, and get to a point where you can just do your checking on the second parameter and ignore the first one, treating it as an accumulator instead of another item you also need to check.
That behaviour is what fold is for - with that function you pass in an initial state (which can be a completely different type from your items, since you're not just smushing them together to create a new value like with reduce) and then on each iteration you get that state and an item.
You can handle the item as you like, and then make changes to the accumulator state depending on the result. Which is exactly the behaviour of your for loop! fold is just a functional way to write one. Tenfour04's answer is how you'd do it - it's the right tool for the job here!

How to "Catch" both a non-numeric and Incomplete ArrayList capacity

I created an ArrayList that has a capacity of 5 Ints. I can get the if statement to run if its less than 5 but I can't seem to get the else statement to "Catch" Non-Numerics. For example if I enter 1,2,3,Hello; it will print "Wrong number of sales provided."
fun main(){
val stats = ArrayList<Int>(5)
println("Enter your numbers: ")
try {
while (stats.size < 5) {
stats.add(readLine()!!.toInt())
}
}catch (e: NumberFormatException){
if (stats.size != 5){
println("The wrong number of sales provided.")
}else{
println("All inputs should be numeric.")
}
exitProcess(status = 1)
}
calStats(stats)
}
fun calStats(sales: Collection<Int>){
val min = sales.minOrNull()
val max = sales.maxOrNull()
println("Min: $min\nMax: $max\nRange: ${(max!! - min!!)}\nAverage: ${(BigDecimal(sales.average()).setScale(0, RoundingMode.FLOOR))} ")
}
The problem is how you are handling your exception, in fact since you are checking the size of your array first, if you enter 1,2,3,'Hello' and there are 4 elements in this list it will output the wrong message.
You should nest your try ... catch block inside the while loop.
Actually the if (stats.size != 5) control is reduntant since the while loop will execute until stats has a size of 5, unless the NumberFormatException is thrown.
Try to edit your code like this:
fun main() {
val stats = ArrayList<Int>(5)
println("Enter your numbers: ")
while (stats.size < 5) {
try {
stats.add(readLine()!!.toInt())
} catch (e: NumberFormatException) {
println("All inputs should be numeric.")
exitProcess(status = 1)
}
}
calStats(stats)
}
Your logic loops through, reading lines and adding them until you've collected 5 values. As soon as it fails at parsing one of those lines as an Int, it throws a NumberFormatException and you hit the catch block.
The first thing the catch block does is check how many values you've successfully added to the stats list. If it's not exactly 5, it prints the "wrong number" error instead of the "inputs need to be numeric" one.
But if you think about it, the size is never going to be 5 when you hit the catch block - if you've added 5 items successfully, the while loop ends and there's no way it's going to throw. If you have 4 items and the 5th one fails, it doesn't get added, so you have 4 items when you hit the catch block.
If you need to do it this way, you probably want to keep a counter of how many lines you've read and refer to that. But you'll still throw once you hit the first non-numeric value (even if there's 5 of them to be read in total) and the counter will show how far you've got, not how many there are.
Probably the easiest way is to read in 5 lines to a list, and then transform them to Ints and add those to your collection. That way you can check if you have less than 5 before you start, and handle that case separately.
Something like
// create a list by calling readline() 5 times - produces null at EOF
val lines = List(5) { readLine() }
if (lines.contains(null)) { // handle your "not enough items" here }
// parse all lines as Ints - any that fail will be null
val stats = lines.map { it.toIntOrNull() } // or map(String::toIntOrNull)
if (stats.contains(null)) { // handle bad values here }
Kotlin's style tries to avoid exceptions, which is why you have functions like toIntOrNull alongside toInt - it lets you use nulls as a "failure value" that you can handle in normal code. But you can always throw an exception if you want (e.g. when you get a null line) and handle it in your catch block.

Counter as variable in for-in-loops

When normally using a for-in-loop, the counter (in this case number) is a constant in each iteration:
for number in 1...10 {
// do something
}
This means I cannot change number in the loop:
for number in 1...10 {
if number == 5 {
++number
}
}
// doesn't compile, since the prefix operator '++' can't be performed on the constant 'number'
Is there a way to declare number as a variable, without declaring it before the loop, or using a normal for-loop (with initialization, condition and increment)?
To understand why i can’t be mutable involves knowing what for…in is shorthand for. for i in 0..<10 is expanded by the compiler to the following:
var g = (0..<10).generate()
while let i = g.next() {
// use i
}
Every time around the loop, i is a freshly declared variable, the value of unwrapping the next result from calling next on the generator.
Now, that while can be written like this:
while var i = g.next() {
// here you _can_ increment i:
if i == 5 { ++i }
}
but of course, it wouldn’t help – g.next() is still going to generate a 5 next time around the loop. The increment in the body was pointless.
Presumably for this reason, for…in doesn’t support the same var syntax for declaring it’s loop counter – it would be very confusing if you didn’t realize how it worked.
(unlike with where, where you can see what is going on – the var functionality is occasionally useful, similarly to how func f(var i) can be).
If what you want is to skip certain iterations of the loop, your better bet (without resorting to C-style for or while) is to use a generator that skips the relevant values:
// iterate over every other integer
for i in 0.stride(to: 10, by: 2) { print(i) }
// skip a specific number
for i in (0..<10).filter({ $0 != 5 }) { print(i) }
let a = ["one","two","three","four"]
// ok so this one’s a bit convoluted...
let everyOther = a.enumerate().filter { $0.0 % 2 == 0 }.map { $0.1 }.lazy
for s in everyOther {
print(s)
}
The answer is "no", and that's a good thing. Otherwise, a grossly confusing behavior like this would be possible:
for number in 1...10 {
if number == 5 {
// This does not work
number = 5000
}
println(number)
}
Imagine the confusion of someone looking at the number 5000 in the output of a loop that is supposedly bound to a range of 1 though 10, inclusive.
Moreover, what would Swift pick as the next value of 5000? Should it stop? Should it continue to the next number in the range before the assignment? Should it throw an exception on out-of-range assignment? All three choices have some validity to them, so there is no clear winner.
To avoid situations like that, Swift designers made loop variables in range loops immutable.
Update Swift 5
for var i in 0...10 {
print(i)
i+=1
}