Exploring the use of range expressions to iterate through alphabet characters
Ranges, provided by the Kotlin standard library, are a powerful solution for implementing iteration and conditional statements in a natural and safe way. A range can be understood as an abstract data type that represents a set of iterable elements and allows iteration through them in a declarative way. The ClosedRange
interface from the kotlin.ranges
package is a basic model of the range data structure. It contains references to the first and last elements of the range and provides the contains(value: T): Boolean
and isEmpty(): Boolean
functions, which are responsible for checking whether the specified element belongs to the range and whether the range is empty. In this recipe, we are going to learn how to declare a range that consists of alphabet characters and iterate through it in a decreasing order.
Getting ready
The Kotlin standard library provides functions that allow the declaration of ranges for the integral, primitive types, such as Int
, Long
, and Char
. To define a new range instance, we can use the rangeTo()
function. For example, we can declare a range of integers from 0
to 1000
in the following way:
val range: IntRange = 0.rangeTo(1000)
The rangeTo()
function has also its own special operator equivalent, ..
, which allows the declaration of a range with a more natural syntax:
val range: IntRange = 0..1000
Also, in order to declare a range of elements in a decreasing order, we can use the downTo()
function.
How to do it...
- Declare a decreasing range of alphabet characters:
'Z' downTo 'A'
2. Create a for
loop to traverse the range:
for (letter in 'Z' downTo 'A') print(letter)
How it works...
As a result, we are going to get the following code printed out to the console:
ZYXWVUTSRQPONMLKJIHGFEDCBA
As you can see, there is also a downTo()
extension function variant for the Char
type. We are using it to create a range of characters from Z
to A
. Note that, thanks for the infix notation, we can omit the brackets while invoking the function—'Z' downTo 'A'
.
Next, we are creating a for
loop, which iterates through the range and prints out the subsequent Char
elements. Using the in
operator, we are specifying the object that is being iterated in the loop—and that's it! As you can see, the Kotlin syntax for the for
loop is neat and natural to use.
Note
Implementations of ranges of the primitive types, such as IntRange
, LongRange
, and CharRange
, also contain Iterator
interface implementations under the hood. They are being used while traversing the range using the for
loop under the hood. In fact, the range implementing the Iterable
interface is called a progression. Under the hood, the IntRange
, LongRange
, and CharRange
classes inherit from the IntProgression
, LongProgression
, and CharProgression
base classes, and they provide the implementations of the Iterator
interface internally.
There's more...
There is also a convenient way to reverse the order of an already-defined progression. We can do so with the extension function provided for the IntProgression
, LongProgression
, and CharProgression
types, which is called reversed()
. It returns new instances of progressions with a reversed order of elements. Here is an example of how to use the reversed()
function:
val daysOfYear: IntRange = 1..365 for(day in daysOfYear.reversed()) { println("Remaining days: $day") }
The preceding for
loop prints the following text to the console:
Remaining days: 365 Remaining days: 364 Remaining days: 363 … Remaining days: 2 Remaining days: 1
The Kotlin standard library offers also another handy extension function called until()
, which allows the declaration of ranges that don't include the last element. It is pretty useful when working with classes that contain internal collections and don't provide elegant interfaces to access them. A good example would be the Android ViewGroup
class, which is a container for the child View
type objects. The following example presents how to iterate through the next indexes of any given ViewGroup
instance children in order to modify the state of each of the children:
val container: ViewGroup = activity.findViewById(R.id.container) as ViewGroup (0 until container.childCount).forEach { val child: View = container.getChildAt(it) child.visibility = View.INVISIBLE }
The until()
infix function helps to make the loop conditions clean and natural to understand.
See also
- This recipe gave us an insight into how Kotlin standard library implementations of ranges for primitives are easy to work with. A problem can appear if we want to traverse non-primitive types using the
for
loop. However, it turns out we can easily declare a range for anyComparable
type. This will be shown in the Building custom progressions to traverse dates recipe.
- As you have noticed, we are using the
in
operator to specify the object that is being iterated in the loop. However, there are also other scenarios where thein
and!in
operators can be used together with ranges. We will investigate them in depth in the Using range expressions with flow control statements recipe.