8
\$\begingroup\$

I have made a function which takes in an end point and a step, then produces a list of integers with the step in mind. For example, if the end point is 10 and the step is 2, the function should return the list [0, 2, 4, 6, 8, 10].

Can I make this function more efficient?

Here is the function:

fun numberStepsFromZero(end: Int, step: Int): MutableList<Int> {
    if (step == 0) {
        throw IllegalArgumentException("Step cannot be 0")
    }

    var currentNumber = 0
    val list = mutableListOf<Int>()
    while (currentNumber <= end) {
        list.add(currentNumber)
        currentNumber += step
    }
    return list
}

Example usage:

val list = try {
    numberStepsFromZero(10, 0)
} catch (e: Exception) {
    Log.d("TAAAG", "$e")
}
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Not a stupid question at all! I removed your caution when I edited. Welcome to Code Review; enjoy learning! \$\endgroup\$ Mar 9, 2022 at 16:33
  • 1
    \$\begingroup\$ BTW, similar functions go under various names in various languages or libraries - e.g. range() in Python, linspace() in Matlab, in APL and iota() in C++. \$\endgroup\$ Mar 9, 2022 at 16:44

3 Answers 3

8
\$\begingroup\$

I don't know this language, so I'll leave syntax-level reviews to others, and focus on the high-level aims.

Some things to consider:

  • You tested for step == 0, which is good - beginners often forget to handle edge cases.
  • Will the code work if step < 0? A first step would be to reject negative steps, but we might want numberStepsFromZero(10, -2) to return [10, 8, 6, 4, 2, 0]. How could we do that?
  • What if end < 0?
  • It's also useful to have a version that starts at a number other than zero.
  • It soon gets hard to test all the different combinations of arguments by hand, so it's worth learning how to create tests that verify that your function produces correct results. Then you can run the test suite after each change.
\$\endgroup\$
4
  • 1
    \$\begingroup\$ For not knowing the language, this is good advice. I added some more language-specific advice in another answer. \$\endgroup\$ Mar 11, 2022 at 14:52
  • \$\begingroup\$ "It's also useful to have a version that starts at a number other than zero." - what "version"? \$\endgroup\$ Mar 11, 2022 at 14:53
  • \$\begingroup\$ By "version", perhaps another function with a different name, perhaps. The Python range() function has an optional/keyword argument for the start value, for example; I don't know whether optional or keyword arguments exist in Kotlin, which is why I wasn't more specific. \$\endgroup\$ Mar 11, 2022 at 14:59
  • \$\begingroup\$ Ah, I read "version" as e.g. "0.1.5" or "1.2.0" 😆 I should have read it as "another function" \$\endgroup\$ Mar 13, 2022 at 20:05
3
\$\begingroup\$

There is a check method for throwing IllegalArgumentExceptions easily.

While it's good for learning purposes, the Kotlin stdlib is full of useful features, including IntProgression which can be used. No need to reinvent the wheel: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/-int-progression/from-closed-range.html

Avoid MutableList when you can. There are other interfaces, such as Sequence which is better for longer lists. Using a sequence builder is an effective way to do that. See code below for how to improve this function:

fun numberStepsFromZero(end: Int, step: Int): Sequence<Int> {
    check(step == 0) { "Step cannot be 0" }
    return sequence {
        var currentNumber = 0
        while (currentNumber <= end) {
            yield(currentNumber)
            currentNumber += step
        }
    }
}

To convert a Sequence into a MutableList, you can simply call .toMutableList() but I would recommend using Sequence when you can, or .toList() when possible.

\$\endgroup\$
3
\$\begingroup\$

First of all, there is a much shorter implementation using existing Kotlin API:

0 .. 10 step 2

This creates an IntProgression, you can then use it as sequence using asSequence or convert to a list using toList. There is also until, which is exclusive from the right side.

If you are still looking for your own implementation, it should be noted, that you can implement this differently.

From your end and step variables you can calculate the final number of items. With that you can create the array/list with final size and then just simply calculate items without the need of while loop, that can most likely be optimized to run faster. I'd implement this something like (naive implementation, not correct if end isn't divisible by step):

0 .. (end / step).map { it * step }

But that also depends on what are you gonna use it for. If you need the whole list, then this will be definitely better, since you can allocate and calculate everything at the beginning and you don't need any kind of reallocations later. If you are gonna use it more like a sequence, never needing all the items at once, then Simon's solution works better.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.