Section 9.2

Searching a text

Cleaning, questioning, locating — and the −1 convention, which is not the zero of absences.

A line read from somewhere never shows up the way you hoped: spaces at the edges, a piece of information to locate before it can be taken. Four methods are enough to put it in order, and they come from the same StrText 9.1 imported.

import { StrText } from "std/extensions/str-text.arc";

extend archetype str StrText;

@entry(start)
object MyProgram {
    start() {
        var line := "  mandrake : 3 doses  ";
        var clean := line.trimmed();

        var known := clean.startsWith(prefix: "mandrake");
        var mark := clean.indexOf(needle: ":", since: 0);

        Console.print(text: $"cleaned: “{clean}”");
        Console.print(text: $"starts with “mandrake”: {known}");
        Console.print(text: $"contains “dose”: {clean.contains(needle: "dose")}");
        Console.print(text: $"the colon sits at index {mark}");
        Console.print(text: $"the ingredient: {clean.substring(start: 0, end: mark).trimmed()}");
        Console.print(text: $"“nightshade”: {clean.indexOf(needle: "nightshade", since: 0)}");
    }
}

trimmed returns a copy, it changes nothing

var clean := line.trimmed();

line has not moved: it is still " mandrake : 3 doses ", spaces included. trimmed returns a text, it does not fix one. That holds for every method in this section, and it holds for str in general: a text, once written, is not retouched — another one is made. Hence the assignment: without it, the work is done and then thrown away.

Answering yes or no

clean.startsWith(prefix: "mandrake")
clean.contains(needle: "dose")

Two bools, which drop straight into an if or combine with the and and or of 3.3. endsWith exists too, the mirror of startsWith.

Locating, and the −1 convention

clean.indexOf(needle: ":", since: 0)

indexOf returns the index of the first occurrence from since onwards — here 9, and the next one can be looked for by starting again at 10. When the needle is nowhere, the answer is -1.

Why -1, when the course presented zero as the mark of absence in 5.2? Because zero is already taken here: it is the index of the very first character, a perfectly valid answer. An absence cannot be marked by a value that already means something else, and -1 — an index that does not exist — is free. So keep the shape:

if (clean.indexOf(needle: "nightshade", since: 0) >= 0) {

and not a comparison against zero, which would miss the first character.

That, in fact, is all contains is: indexOf(…) >= 0, written once so it need not be thought about again.

Locating, then cutting

The last two lines of the program do together what all this is for:

clean.substring(start: 0, end: mark).trimmed()

indexOf gave the index of the colon, substring takes what precedes it, trimmed removes the space left at the edge — and mandrake remains. The methods chain because each returns a text, and a text can do everything StrText has taught it.

Over to you

  • Print line.length then clean.length: the first has lost nothing.
  • Look for ":" from 10 rather than from 0: there is no second colon, so -1.
  • Replace >= 0 with > 0 in the if above and look for "mandrake": the condition is false although the word is right there, at index zero. That is exactly the trap the -1 convention asks you to avoid.
  • Take what follows the colon — substring(start: mark + 1, end: clean.length) — and clean it up. That piece, 3 doses, is the subject of the next subsection.