Finding text inside a string
Often you need to know where a piece of text appears in a string. Kotlin strings answer with indexOf, which returns the index of the first character of the first match, or -1 when the text is not there.
val sentence = "Hello World"
println(sentence.indexOf("World")) // 6
println(sentence.indexOf("xyz")) // -1
The match "World" starts at the W, which is at index 6 (Hello takes indexes 0 to 4, the space is 5). The index points to the first character of the match, not the last.
The value -1 is a signal, not a position. Valid indexes are never negative, so it is safe to use as "not found". Always compare against it before you use the result as an index.
The three common forms
val s = "Hello World"
println(s.indexOf("o")) // 4 first match
println(s.lastIndexOf("o")) // 7 last match
println(s.indexOf("o", 5)) // 7 first match at index 5 or later
indexOf(sub)scans from the start and returns the first match.lastIndexOf(sub)scans from the end and returns the start index of the last match.indexOf(sub, startIndex)begins the search atstartIndex. Searching again from the previous match plus one is how you find every occurrence.
You can search for a single Char as well as a String: s.indexOf('o') gives the same answer as s.indexOf("o").
Case matters
The search is case-sensitive by default. The lowercase "world" is not in "Hello World":
val s = "Hello World"
println(s.indexOf("world")) // -1
println(s.indexOf("world", ignoreCase = true)) // 6
Kotlin's indexOf has an ignoreCase parameter for this. It is a named argument, so write it with its name. Passing it as the second argument without the name would mix it up with the start index.
The exercise
The lesson provides a sentence and asks for one new variable:
- Decide what to search for. The text is
"World", passed as the argument toindexOf. - Store the result. It is an
Int, and it never changes afterwards, so it is declared withvaland namedidx.
val sentence = "Hello World"
val idx = sentence.indexOf("World")
Kotlin infers the type of idx as Int from the value, so no annotation is needed. Its value is 6.
Finding every occurrence
To visit all the matches, search again from just after each one you find. The loop stops when indexOf returns -1:
val text = "banana"
var pos = text.indexOf("ana")
while (pos != -1) {
println(pos)
pos = text.indexOf("ana", pos + 1)
}
This prints 1 and then 3. The two matches share the a at index 3, and starting at pos + 1 finds both. Starting at pos + "ana".length would skip the overlapping one. Here pos is a var because it changes on every pass.
The same loop works for counting matches: add one to a counter on each pass. Keep the loop condition and the update together, so the search always moves forward and cannot repeat the same match forever.
Using the result safely
A found index is often used straight away, for example to cut the text after the match with substring. That is where the -1 becomes dangerous: with no match, the next step would receive a negative index. Check first:
val sentence = "Hello World"
val idx = sentence.indexOf("World")
if (idx != -1) {
println(sentence.substring(idx)) // World
} else {
println("not found")
}
If you only need a yes-or-no answer, contains says it more directly: sentence.contains("World") is true. startsWith and endsWith do the same for the two ends of the string.
Common mistakes
- Treating
-1as a real index. Passing it on tosubstringor to[ ]throws an exception. - Forgetting the case. A search for
"world"fails on"Hello World"unless you ask to ignore case. - Expecting the end of the match. The index is where the match starts. The end is
idx + sub.length. - Expecting every match.
indexOfreturns only the first one. Use a loop with the start-index form, orlastIndexOffor the final one. - Discarding the result. Calling
sentence.indexOf("World")without storing or printing it computes the number and throws it away.
Key takeaways
s.indexOf(sub)returns the index of the first match, or-1.s.lastIndexOf(sub)finds the last match;s.indexOf(sub, from)starts the search atfrom.- The search is case-sensitive unless you pass
ignoreCase = true. - Check for
-1before using the index.