14. Working with text

Chapter 12 covered what Go gives you directly for strings: joining with +, length with len, indexing, and slicing. This chapter adds the tools for changing case, repeating, searching and replacing, which live in the strings package. It also covers fmt.Sprintf and the fmt markers, and the strconv package, which moves between text and numbers.

Using the strings package

Import strings next to fmt:

import (
    "fmt"
    "strings"
)

Functions in it take the string to work on as an argument. Those that produce text give back a new string. The original is never changed, because in Go strings cannot be changed.

Changing case

strings.ToUpper and strings.ToLower give a new string in upper or lower case:

name := "Keiko"
fmt.Println(strings.ToUpper(name)) // KEIKO
fmt.Println(strings.ToLower(name)) // keiko
fmt.Println(name)                  // Keiko  (still the original)

Repeating a string

strings.Repeat repeats a string that many times:

fmt.Println(strings.Repeat("-", 20))
fmt.Println(strings.Repeat("ab", 3))
fmt.Println(strings.Repeat("=", 4) + " TITLE " + strings.Repeat("=", 4))

Output:

--------------------
ababab
==== TITLE ====

It is the right tool for drawing borders or padding text. These examples are in exercises/14/02-case-and-repeat.go.

Searching with Contains and Index

The simplest way to check whether a string contains something is strings.Contains, which gives true or false:

fmt.Println(strings.Contains("hello world", "world")) // true
fmt.Println(strings.Contains("hello world", "z"))     // false

Because it gives a bool, it pairs naturally with if, which Chapter 16 teaches. For now, print the result.

When you also need to know where the match is, use strings.Index. It gives the index of the first match, or -1 if there is none:

fmt.Println(strings.Index("hello world", "world")) // 6
fmt.Println(strings.Index("hello world", "z"))     // -1

To test for presence, use strings.Contains — it is clearer. Use strings.Index when you need the position number.

Replacing with ReplaceAll

strings.ReplaceAll(s, old, new) gives a new string with every occurrence of old swapped for new:

fmt.Println(strings.ReplaceAll("hello", "l", "L")) // heLLo
fmt.Println(strings.ReplaceAll("a-b-c", "-", " ")) // a b c

The original string is untouched. If you want to keep the result, store it:

message := "meet me at noon"
dashed := strings.ReplaceAll(message, " ", "-")
fmt.Println(dashed) // meet-me-at-noon

The searching and replacing examples are in exercises/14/03-search-and-replace.go.

strings.Replace does the same but takes a count as a fourth argument, the most replacements to make. strings.Replace("hello", "l", "L", 1) gives heLlo.

Other useful functions

The strings package has many functions. A few you will reach for often:

Function What it does
strings.TrimSpace(s) Remove leading and trailing whitespace.
strings.Fields(s) Split on whitespace, into a list of words.
strings.Split(s, sep) Split on a specific separator.
strings.HasPrefix(s, t) true if s starts with t.
strings.HasSuffix(s, t) true if s ends with t.
padded := "  hello  "
fmt.Println(strings.TrimSpace(padded)) // hello

words := strings.Fields("one two three")
fmt.Println(words) // [one two three]

parts := strings.Split("a,b,c", ",")
fmt.Println(parts) // [a b c]

strings.Fields and strings.Split give back a list of values, also called a slice, which Chapter 22 covers. For now, just print it.

Formatting with fmt.Printf and fmt.Sprintf

When you need numbers or text plugged into specific spots, fmt.Printf is the standard approach. You have met some of its markers already. Go calls them verbs:

Verb What it does Example output
%s a string Keiko
%d a whole number 7
%.2f a float64 with two decimals 3.14
%v any value, in its usual form Keiko or 7
%T the type of the value float64
%-10s a string, padded to width 10 Keiko
%5d a number, padded to width 5 7
name := "Keiko"
level := 7
hp := 95
fmt.Printf("%s (Lv %d) HP %d\n", name, level, hp)

Output:

Keiko (Lv 7) HP 95

fmt.Sprintf takes the same arguments but does not print. It gives you the finished string, to store or join (the S is for "string"):

name := "Keiko"
level := 7
line := fmt.Sprintf("%s is level %d", name, level)
fmt.Println(line) // Keiko is level 7

A wrong verb is not a compile error, so the program runs and prints something odd where the value should be. fmt.Printf("%d\n", "Keiko") prints %!d(string=Keiko). If you see %! in your output, compare the verbs with the values after them.

Open exercises/14/01-format.go. Change the first fmt.Printf so the output reads Keiko has 95 HP at level 7.

Turning numbers into text and text into numbers

The strconv package ("string conversion") moves between the two. strconv.Itoa turns an int into a string, which lets you use +:

level := 7
fmt.Println("Level " + strconv.Itoa(level) + " complete") // Level 7 complete

strconv.Atoi goes the other way, and it gives back two values: the number, and a report of whether the conversion worked. You met two values on one line in Chapter 11. Chapter 21 explains the report, which is called an error. Until then, throw it away by writing _, the blank identifier, in its place. Unlike a variable, _ never has to be used:

n, _ := strconv.Atoi("42")
fmt.Println(n + 1) // 43

For a decimal, use strconv.ParseFloat. The second argument, 64, means you want a float64:

x, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(x * 2) // 6.28

If the text cannot become a number, the result is 0, with no crash:

n, _ := strconv.Atoi("hello")
fmt.Println(n) // 0

Throwing away the report means your program cannot tell this 0 from a real 0. That is acceptable for now, and Chapter 21 fixes it.

You will use these conversions a lot in the next chapter, where the program reads keyboard input. Whatever the user types arrives as a string, even if they typed digits. All of this is in exercises/14/04-text-and-numbers.go.

string(level) looks like the right conversion, but it is not. It gives the character stored under that number, not the digits. strconv.Itoa is the one that turns 7 into "7".

Homework

Homework files are in exercises/14/homework/.

Problem 1 — Loud and quiet

Open exercises/14/homework/01-loud-and-quiet.go. A starter variable holds a name. Print it three times: as it is, in upper case, then in lower case.

Problem 2 — Contains

Open exercises/14/homework/02-contains.go. A variable holds a sentence. Use strings.Contains to print whether it contains the word go, then whether it contains the word cake.

Problem 3 — Censor

Open exercises/14/homework/03-censor.go. A variable holds a short message. Use strings.ReplaceAll to swap every space with a dash -, then print it.

Challenge — Title block

Open exercises/14/homework/04-title-block.go. A variable holds a title (any string you like). Print a block like this, where the top and bottom dash rows match the length of the row with the title:

---------------
-- INVENTORY --
Inside text goes here
-- INVENTORY --
---------------

Use strings.Repeat and len to size the borders without counting by hand. Changing the title should change the border length automatically.

Stuck or finished? Open the homework solutions page.