• No se han encontrado resultados

Of all the features discussed in this chapter, iteration in Kotlin is probably the most similar to Java. Thewhile loop is identical to the one in Java, so it deserves only a brief mention in the beginning of this section. Thefor loop exists in only one form, which is equivalent to Java’s 'for-each' loop. It’s written for <item> in <elements>, as in C#.

The most common application of this loop is iterating over collections, just as in Java.

We’ll explore how it can cover other looping scenarios as well.

2.4.1 The while Loop

Kotlin has while and do-while loops, and their syntax doesn’t differ from the corresponding loops in Java:

The body is executed while the condition is true.

The body is executed for the first time unconditionally. After that, it’s executed while the condition is true.

Kotlin doesn’t bring anything new to these simple loops, so we won’t linger. Let’s move on to discuss the various uses of theforloop.

2.4.2 Iterating over numbers: ranges and progressions

As we just mentioned, in Kotlin there’s no regular Javafor loop, where you initialize a variable, update its value on every step through the loop, and exit the loop when the value reaches a certain bound. To replace the most common use cases of such loops, Kotlin uses the concepts of ranges.

A range is essentially just an interval between two values, usually numbers: a start and an end. You write it using the..operator:

Note that ranges in Kotlin are closed or inclusive, meaning the second value is also always a part of the range.

The most basic thing you can do with integer ranges is loop over all the values. If you can iterate over all the values in a range, such a range is called a progression.

while (condition) { /*...*/

}

do {/*...*/

} while (condition)

val oneToTen = 1..10

Let’s use integer ranges to play the Fizz-Buzz game. It’s a nice way to survive a long trip in a car and remember your forgotten division skills. Players take turns counting incrementally, replacing any number divisible by three with the word fizz and any number divisible by five with the word buzz. If a number is a multiple of both three and five, you say "FizzBuzz."

The following code prints the right answers for the numbers from 1 to 100. Note how you check the possible conditions with awhenexpression without an argument:

If i is divisible by 15, returns FizzBuzz. As in Java, % is the modulus operator.

If i is divisible by 3, returns Fizz If i is divisible by 5, returns Buzz Else returns the number itself

Iterates over the integer range 1..100

Suppose you get tired of these rules after an hour of driving and want to complicate things a bit. Let’s start counting backward from 100 and include only even numbers:

Now you’re iterating over a progression that has a step, which allows it to skip some numbers. The step can also be negative, in which case the progression goes backward rather than forward. In this example,100 downTo 1 is a progression that goes backward (with step -1). Thenstep changes the absolute value of the step to 2 while keeping the direction (in effect, setting the step to -2).

As we mentioned earlier, the .. syntax always creates a range that includes the end point (the value to the right of..). In many cases, it’s more convenient to iterate over

fun fizzBuzz(i: Int) = when { i % 15 == 0 -> "FizzBuzz

" i % 3 == 0 -> "Fizz "

i % 5 == 0 -> "Buzz "

else -> "$i "

}

>>> for (i in 1..100) { ... print(fizzBuzz(i)) ... }

}1 2 Fizz 4 Buzz Fizz 7 …

>>> for (i in 100 downTo 1 step 2) { ... print(fizzBuzz(i))

... }

Buzz 98 Fizz 94 92 FizzBuzz 88 …

half-closed ranges, which don’t include the specified end point. To create such a range, use theuntil function. For example, the loop for (x in 0 until size)is equivalent to for (x in 0..size-1), but it expresses the idea somewhat more clearly. Later, in section XREF ID_infix_calls, you’ll learn more about the syntax for downTo, step, and

until in these examples. You can see how working with ranges and progressions helped you cope with the advanced rules for the FizzBuzz game. Now let’s look at other examples that use theforloop.

2.4.3 Iterating over maps

We’ve mentioned that the most common scenario of using afor .. in loop is iterating over a collection. This works exactly as it does in Java, so we won’t say much about it.

Let’s see how you can iterate over a map, instead.

As an example, we’ll look at a small program that prints binary representations for characters. You store these binary representations in a map (just for illustrative purposes).

You create a map, fill it with binary representations of some letters, and then print the map’s contents:

Uses TreeMap so that the keys are sorted

Iterates over the characters from A to F using a range of characters Converts ASCII code to binary

Stores the value in a map by the c key

Iterates over a map, assigning the map key and value to two variables

The.. syntax to create a range works not only for numbers, but also for characters.

Here you use it to iterate over all characters from 'A' up to and including 'F'.

The example shows that thefor loop allows you to unpack an element of a collection you’re iterating over (in this case, a collection of key/value pairs in the map). You store

val binaryReps = TreeMap<Char, String>()

for (c in 'A'..'F') {

val binary = Integer.toBinaryString(c.toInt()) binaryReps[c] = binary

}

for ((letter, binary) in binaryReps) { println("$letter = $binary") }

the result of the unpacking in two separate variables: letter receives the key, and

binary receives the value. Later in section XREF ID_destructuring_declarations you’ll find out more about this unpacking syntax.

Another nice trick used in this example is the shorthand syntax for getting and updating the values of a map by key. Instead of calling get() and put(), you can use

map[key]to read values andmap[key] = valueto set them. The code

binaryReps[c] = binary

is equivalent to its Java version:

binaryReps.put(c, binary)

The output is similar to the following (we’ve arranged it in two columns instead of one):

You can use the same unpacking syntax to iterate over a collection while keeping track of the index of the current item. You don’t need to create a separate variable to store the index and increment it by hand:

Iterates over a collection with an index The code prints what you expect:

We’ll dig into the whereabouts ofwithIndex in the next chapter.

You’ve seen how you can use thein keyword to iterate over a range or a collection.

You can also useinto check whether a value belongs to the range or collection.

2.4.4 Using an 'in' check

You use thein operator to check whether a value is in a range, or its opposite, !in, to check if a value isn’t in a range. Here’s how you can use

belongs to a range of characters:

in to check if a character

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' A = 1000001 D = 1000100

B = 1000010 E = 1000101 C = 1000011 F = 1000110

val list = arrayListOf("10", "11", "1001") for ((index, element) in list.withIndex()) {

println("$index: $element") }

0: 10 1: 11 2: 1001

fun isNotDigit(c: Char) = c !in '0'..'9'

>>> println(isLetter('q')) true>>> println(isNotDigit('x')) true

This technique for checking whether a character is a letter looks simple. Under the hood, nothing tricky happens: you still check that the character’s code is somewhere between the code of the first letter and the code of the last one. But this logic is concisely hidden in the implementation of the range classes in the standard library:

Transforms to a c && c z

Theinand!inoperators also work inwhenexpressions:

Checks whether the value is in the range from 0 to 9 You can combine multiple ranges.

Ranges aren’t restricted to characters, either. If you have any class that supports comparing instances (by implementing the java.lang.Comparable interface), you can create ranges of objects of that type. If you have such a range, you can’t enumerate all objects in the range. Think about it: can you, for example, enumerate all strings between

"Java" and "Kotlin"? No, you can’t. But you can still check whether another object belongs to the range, using theinoperator:

The same as "Java" "Kotlin" && "Kotlin" "Scala"

Note that the strings are compared alphabetically here, because that’s how theString

class implements theComparable interface.

c in 'a'..'z'

fun recognize(c: Char) = when (c) { in '0'..'9' -> "It's a digit!"

in 'a'..'z', in 'A'..'Z' -> "It's a letter!"

else -> "I don't know…"

}>>> println(recognize('8')) It's a digit!

>>> println("Kotlin" in "Java".."Scala") true

The sameincheck works with collections as well:

This set doesn’t contain the string "Kotlin". Later, in section , you’ll see how to use ranges and progressions with your own data types and what objects in general you can use in checks with.

There’s one more group of Java statements we want to look at in this chapter:

statements for dealing with exceptions.