Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

A range is a sequence of values defined by a start, an end, and a step.

In this quick tutorial, we’ll have a look at how we can define and use ranges in Kotlin.

2. Using Kotlin Ranges

In Kotlin, we can create ranges using the rangeTo() and downTo() functions or the .. operator.

We can use ranges for any comparable type.

By default, they’re inclusive, which means that the 1..4 expression corresponds to the values 1,2,3 and 4.

In addition, there’s another default: the distance between two values, called a step, with an implicit value of 1.

So now, let’s take a look at a few examples of creating ranges and using other useful methods to manipulate them.

2.1. Creating Ranges

Ranges implement a common interface – ClosedRange<T>. The result of a ClosedRange is a progression (such as IntProgression, LongProgression, or CharProgression).

This progression contains a start, an inclusive end, and a step and it’s a subtype of Iterable<N> where N is Int, Long or Char.

Let’s start by looking at the simplest way to create a range, using the “..” and in operators:

(i in 1..9)

Also, if we want to define a backward range we can use the downTo operator:

(i in 9 downTo 1)

We can also use this expression as part of an if statement to check if a value belongs to a range:

if (3 in 1..9)
  print("yes")

2.2. Iterating Ranges

Now, while we can use ranges with anything comparable, if we want to iterate, then we need an integral type range.

Now let’s take a look at the code to iterate through a range:

for (i in 1.rangeTo(9)) {
    print(i) // Print 123456789
}
  
for (i in 9.downTo(1)) {
    print(i) // Print 987654321
}

The same use case applies to chars:

for (ch in 'a'..'f') {
    print(ch) // Print abcdef
}
  
for (ch in 'f' downTo 'a') {
    print(ch) // Print fedcba
}

3. Using the step() Function

The use of the step() function is fairly intuitive: we can use it to define a distance between the values of the range:

for(i in 1..9 step 2) {
    print(i) // Print 13579
}

for (i in 9 downTo 1 step 2) {
    print(i) // Print 97531
}

In this example, we’re iterating forward and backward through the values from 1-9, with a step value of 2.

4. Using the reversed() Function

As the name suggests, the reversed() function will reverse the order of the range:

(1..9).reversed().forEach {
    print(it) // Print 987654321
}

(1..9).reversed().step(3).forEach {
    print(it) // Print 963
}

5. Using the until() Function

When we want to create a range that excludes the end element we can use until():

for (i in 1 until 9) {
    print(i) // Print 12345678
}

6. The last, first, step Elements

If we need to find the first, the step or the last value of the range, there are functions that will return them to us:

print((1..9).first) // Print 1
print((1..9 step 2).step) // Print 2
print((3..9).reversed().last) // Print 3

7. Filtering Ranges

The filter() function will return a list of elements matching a given predicate:

val r = 1..10
val f = r.filter { it -> it % 2 == 0 } // Print [2, 4, 6, 8, 10]

We can also apply other functions such as map() and reduce() to our range:

val m = r.map { it -> it * it } // Print [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
val rdc = r.reduce{a, b -> a + b} // Print 55

8. Other Utility Functions

There are many other functions we can apply to our range, like min, max, sum, average, count, distinct:

val r = 1..20
print(r.min()) // Print 1
print(r.max()) // Print 20
print(r.sum()) // Print 210
print(r.average()) // Print 10.5
print(r.count()) // Print 20

val repeated = listOf(1, 1, 2, 4, 4, 6, 10)
print(repeated.distinct()) // Print [1, 2, 4, 6, 10]

9. Custom Objects

It’s also possible to create a range over custom objects. For that, the only requirement is to extend the Comparable interface.

An enum is a good example. All enums in Kotlin extend Comparable which means that, by default, the elements are sorted in the sequence they appear.

Let’s create a quick Color enum:

enum class Color(val rgb: Int) : Comparable<Color> {
    BLUE(0x0000FF),
    GREEN(0x008000),
    RED(0xFF0000),
    MAGENTA(0xFF00FF),
    YELLOW(0xFFFF00);
}

And then use it in some if statements:

val range = red..yellow
if (range.contains(Color.MAGENTA)) println("true") // Print true
if (Color.RED in Color.GREEN..Color.YELLOW) println("true") // Print true
if (Color.RED !in Color.MAGENTA..Color.YELLOW) println("true") // Print true

However, as this is not an integral type, we can’t iterate over it. If we try, we’ll get a compilation error:

fun main(args: Array<String>) {
    for (c in Color.BLUE.rangeTo(Color.YELLOW)) println(c) // for-loop range must have an iterator() method
}

And if we do want to have a custom range that we can iterate over, we just need to implement ClosedRange as well as Iterator.

10. Conclusion

In this article, we demonstrated how we can use range expressions in Kotlin and different functions we can apply.

As always the source code is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of