I'm starting to do a lot of interviews in kotlin. ...
# getting-started
c
I'm starting to do a lot of interviews in kotlin. one thing that always trips me up is for loops in kotlin when iterating over arrays/lists. i can never remember the syntax. I get stuck thinking to myself
for (i in list.indices)
or should i do
for(i in 0 .. list.size)
or
for(i in 0 until list.size)
etc. I know it sounds dumb/basic. but anyone have any tricks they use to remember these. I feel like i should use the .. or until version of a for loop
j
why not simply use
for (item in list)
?
which is a bit easier to remember
v
Or
list.forEach *{ ... }*
Or if you also need the index,
list.forEachIndexed { ... }
2
a
I'm a fan of using
forEachIndexed
or
list.withIndex()
for normal for loops. That's more _kotlin_; anything else reduces readability imo Also, some colleagues of mine still get momentarily confused with
..
and
until
. We found it easier to just avoid it whenever possible.
c
In these leetcode interviews were typically asked to solve problems that involve indexes and such. like finding the two number sum in a list. would require two for loops with offsets
like... this is hard to understand IMO
Copy code
fun twoNumberSum(array: MutableList<Int>, targetSum: Int): List<Int> {
for (i in 0 until array.size - 1) {
val firstNum = array[i]
for (j in i + 1 until array.size) {
val secondNum = array[j]
if (firstNum + secondNum == targetSum) {
return listOf<Int>(firstNum, secondNum)

return listof<Int>()
}
Copy code
i in 0 until array.size - 1
and
Copy code
j in i + 1 until array.size
why not use
..
maybe im just too used to java for leetcode style interviews. 😭
a
That's why I personally never use
for
loops and only
forEach/forEachIndexed
c
Can you use a forEachIndexed with the second for loop in that example snippet above?
a
j in 0..<array.size
, the new until operator is made for that kind of loops !
a
You can't
break
out of
forEach
though (return@forEach is
continue
), without using an outer label
a
Yes, with nested loops I always put labels to each loops, which is pretty readable and not too verbose
w
Since Kotlin 1.9, we now have
..<
, which is arguably more concise
K 2
K 3
r
I never use Kotlin for leetcode. I always use Java
😅 2
a
it is indeed difficult to just read the source code and quickly understand whether
until
means ≤ or <. IntelliJ adds some helpful hints.
w
If you have to do Leetcode stuff, for me personally, there is no better language than Kotlin. I did quite a lot of problems using Kotlin, and I'd say I would not reach the same productivity in any other language. Over time you'll build your own collection of functions that you end up needing a lot. But the standard library will have your back most of the time.
For this problem, you have an optimization that cuts down the
n*n
operations to
n*n / 2
. But I would say that you can always start simple. My collection of leetcode util functions for example has a
combinations
function. Which gives the combination of all options between 2 iterables. I could use this to solve the
twoNumberSum
problem.
Copy code
fun twoNumberSum(array: MutableList<Int>, targetSum: Int): List<Int> {
  return array.withIndex().combinations(array.withIndex())
    .filter { (left, right) -> left.index != right.index }
    .firstOrNull { (left, right) -> left.value + right.value == targetSum }
    ?.let { (left, right) -> listOf(left.index, right.index) }
    ?: throw AssumptionBrokenException(assumption = "Only one valid answer exists")
}
And you might worry about the performance, because this example does not have your
/ 2
optimization. But both of our implementations have
O(N)
performance. To really gain performance, you need to think about the right algorithm. And in this case it's actually that you can utilize a lookup table like shown in the following example:
Copy code
fun twoNumberSum(array: MutableList<Int>, targetSum: Int): List<Int> {
    val searchingValuesToIndex = array.withIndex().associate { (targetSum - it.value) to it.index }
    array.forEachIndexed { index, value ->
        val indexOfMatchingValue = searchingValuesToIndex[value] ?: return@forEachIndexed
        if (indexOfMatchingValue != index) return listOf(index, indexOfMatchingValue)
    }
    throw AssumptionBrokenException(assumption = "Only one valid answer exists")
}
This will have
O(N)
performance. If you skipped it all, remember this: Most interviewers will appreciate it if you can solve the problem without needlessly fiddling with indexes.
e
Kotlin optimizes
Copy code
for ((i, item) in list.withIndices))
to avoid the construction of an unnecessary wrapper iterator, and even if it didn't, it would be the clearer way to write it anyway
💯 1
v
You think
for ((i, item) in list.withIndices)) { ... }
is clearer than
list.forEachIndexed { i, item -> ... }
?
e
yes I do
🤷‍♂️ 1
it also allows for control flow operators like
break
and
continue
which do not with the functional extension
3
I only use
.forEach*
at the end of a nullable or long chain
j
I also think
for
loops are often more readable than many functional constructs. If you think it is not readable enough, remember destructuring is not mandatory and can sometime affect readability.
forEach
is great when you have a function to provide to it, but instead of using lambdas with it I prefer switching to a
for
loop
l
If you watch the advent of code Kotlin videos, many of these problems can be solved without index using things like
chunked
,
windowed
, and other collection methods. They often make your code cleaner, shorter, and more idiomatic, at the cost of having to memorize them.
💯 1
f
when you use
array.size - 1
consider instead
array.lastIndex
1
c
wait. so is there a way to write this code
Copy code
for (i in 0 until array.size - 1) {
val firstNum = array[i]
for (j in i + 1 until array.size) {
val secondNum = array[j]
using forEach? I thought if I need to "offset" my iterators then I kinda have no choice but to use the "primitive" version of a for loop instead of a functional forEach call.
l
Copy code
array.forEachIndexed { first, i ->
    array.drop(i).forEach { second ->
        ...
    }
}
f
this is inneficient though, the
drop
call makes a new array list
l
If that matters for the use case, then subList exists on List.
f
sure, but that's more convoluted, I don't think you gain anything in terms of readability by dropping/sublisting the original array
l
It helps to avoid tracking indices, removing an avenue for bugs.
f
well, you are still using the first index for the drop/sublist call
l
Also, depending on what it is you're doing, you may not even need the inner loop.
Copy code
array.mapIndexed { first, i ->
    array.drop(i).sumOf { it + first }
}.sum()
Or
Copy code
array.mapIndexedNotNull { first, i ->
    array.drop(i).firstOrNull { it matches some condition }
}.first()
Or some other simplification.
e
instead of
drop
,
Copy code
for ((i, first) in list.withIndices()) {
    for (second in list.subList(i + 1)) {
gets there without excessive copying