何かプロダクトを作成する際に文字列を操作したり,検索したり,といった処理が必要になることはよくあります.Go言語の標準ライブラリであるstringsパッケージには,文字列操作を効率よく行うための関数が数多く用意されている.この記事では,主要な関数ごとに仕様や使用例を詳しく解説する.
Goのバージョンによっては一部の関数の挙動や追加有無が変化することがあるため,使用時には公式ドキュメントを参照するとよい.
シリーズ Project Gopher: Unlocking Go’s Secrets
Part1 context.Contextってなんなんだ
他のシリーズ記事
Goを知らない人は以下の記事から.
上の記事も〇〇チートシートとしてシリーズ化しているのでぜひご覧ください.様々な言語,フレームワーク,ライブラリなど開発技術の使用方法,基本事項,応用事例を網羅し,手引書として記載したシリーズです.
git/gh,lazygit,docker,vim,typescript,SQL,プルリクエスト/マークダウン,ステータスコード,ファイル操作,OpenAI AssistantsAPI,Ruby/Ruby on Rails のチートシートがあります.以下の記事に遷移した後,各種チートシートのリンクがあります.
TypeScriptで学ぶプログラミングの世界
プログラミング言語を根本的に理解するシリーズです.
情報処理技術者試験合格への道[IP・SG・FE・AP]
情報処理技術者試験の単語集です.
IAM AWS User クラウドサービスをフル活用しよう!
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです.
インポート方法
まずはstringsパッケージをインポートする.標準ライブラリなので以下のようにimportするだけで使える.
import (
"fmt"
"strings"
)
以下では,頻繁に使用される関数を1つずつ取り上げ,シグネチャ,説明,使用例,具体的な注意点やテクニックなどを詳しく紹介する.
1. Contains
シグネチャ
func Contains(s, substr string) bool
説明
sの中にsubstrが含まれていればtrueを返し,含まれていなければfalseを返す.調べたい文字列に特定の部分文字列が存在するかどうかを確かめるのに便利だ.
使用例
s := "Go is awesome!"
fmt.Println(strings.Contains(s, "some")) // true
fmt.Println(strings.Contains(s, "SoMe")) // false (大文字小文字は区別される)
大文字と小文字は区別されるため,ケースを無視して判定したいときにはstrings.EqualFoldなどを併用するか,一旦すべて小文字または大文字に変換してからContainsを使うという手がある.
2. ContainsAny
シグネチャ
func ContainsAny(s, chars string) bool
説明
文字列sにchars中のいずれか1文字でも含まれていればtrueを返す
使用例
s := "Gopher"
fmt.Println(strings.ContainsAny(s, "xyz")) // false
fmt.Println(strings.ContainsAny(s, "abcPh")) // true ('h'が含まれている)
複数の文字のいずれか1つでも含まれているかを手軽に判定できる.
逆に,特定の文字群以外から成り立っているかどうかをチェックしたい場合は,ループや正規表現を活用することも考慮するとよい.
3. ContainsRune
シグネチャ
func ContainsRune(s string, r rune) bool
説明
文字列s
の中に,指定したルーンr
が含まれていればtrueを返す.文字列がマルチバイト文字を含む場合でも正しく判定する.
使用例
s := "こんにちは"
fmt.Println(strings.ContainsRune(s, 'に')) // true
fmt.Println(strings.ContainsRune(s, 'あ')) // false
文字列をルーン単位で判定できる.
Go言語では,文字=runeという扱い方をする場合が多いため,ContainsRuneはマルチバイト文字の有無を判別するのに便利だ.
4. Count
シグネチャ
func Count(s, sep string) int
説明
文字列sの中で,sep
が出現する回数を返す.重複している場合も数え上げられる.
使用例
s := "banana"
fmt.Println(strings.Count(s, "a")) // 3
fmt.Println(strings.Count(s, "na")) // 2
空文字列("")をsepに指定すると特別な動作があり,全ての間に空文字列が存在するとみなしてs.Len()+1を返すことに注意.
主に単純な部分文字列が何回出現するかを調べたい時に使う.
5. EqualFold
シグネチャ
func EqualFold(s, t string) bool
説明
大文字・小文字を区別せずにs
とt
が等しいかどうかを判定する.たとえばGoLang
とgolang
のようにケースが異なる文字列に対してもtrueを返す.
使用例
fmt.Println(strings.EqualFold("GoLang", "golang")) // true
fmt.Println(strings.EqualFold("GoLang", "Golang!")) // false
完全にケースを無視して文字列を比較したい時に使うと便利だ.
Unicodeのケースに対しても対応しているため,よりグローバルな需要にも応えられる.
6. Fields
シグネチャ
func Fields(s string) []string
説明
連続したホワイトスペースを区切り文字として,文字列sを分割し,分割後のスライスを返すだ.
使用例
s := " Go is an open-source language "
fmt.Println(strings.Fields(s))
// ["Go", "is", "an", "open-source", "language"]
Fieldsは空白文字(タブや改行も含む)を全て区切りとして扱う.
CSVのように特定の文字で分割したい場合にはSplitやSplitNを使う.
7. FieldsFunc
シグネチャ
func FieldsFunc(s string, f func(rune) bool) []string
説明
判定関数fがtrueを返す文字を区切りとして文字列を分割し,結果のスライスを返す.より柔軟な分割ロジックを組める.
使用例
s := "apple,banana;orange"
splitFunc := func(r rune) bool {
return r == ',' || r == ';'
}
result := strings.FieldsFunc(s, splitFunc)
fmt.Println(result)
// ["apple", "banana", "orange"]
カンマやセミコロンだけでなく,任意のルーンで分割できるため,大量の区切り文字への対応や細かいカスタマイズが必要な場合に便利だ.
正規表現を使わずに複雑な分割を実現できる.
8. HasPrefix
シグネチャ
func HasPrefix(s, prefix string) bool
説明
文字列s
がprefix
で始まっていればtrueを返す.先頭の部分一致を素早くチェックできる.
使用例
s := "golang"
fmt.Println(strings.HasPrefix(s, "go")) // true
fmt.Println(strings.HasPrefix(s, "lang")) // false
ファイルパスの拡張子チェックや,URLのプロトコルチェックに使える.
大文字小文字の違いは区別されるため,ケースを無視する場合は別途ToLower等が必要.
9. HasSuffix
シグネチャ
func HasSuffix(s, suffix string) bool
説明
文字列s
がsuffix
で終わっていればtrueを返す.
使用例
s := "golang"
fmt.Println(strings.HasSuffix(s, "lang")) // true
fmt.Println(strings.HasSuffix(s, "go")) // false
ファイル名の拡張子チェックなどによく使われる.
こちらも大文字小文字は区別される.
10. Index
シグネチャ
func Index(s, substr string) int
説明
s
の中でsubstr
が最初に見つかった位置(インデックス)を返す.文字が見つからない場合は-1を返す.
使用例
s := "Hello Gopher"
fmt.Println(strings.Index(s, "o")) // 4
fmt.Println(strings.Index(s, "xyz")) // -1
利用例としては,特定の文字列が最初に現れる位置を調べて,後続の文字を切り出すなどの処理が挙げられる.
最初ではなく最後に見つかった位置を探したい場合はLastIndexを使う.
11. IndexAny
シグネチャ
func IndexAny(s, chars string) int
説明
sの中でchars中のいずれかの文字が最初に出現した位置を返す.見つからなければ-1を返す.
使用例
s := "Gopher"
fmt.Println(strings.IndexAny(s, "abcPh")) // 1 ('o'が該当)
fmt.Println(strings.IndexAny(s, "xyz")) // -1
複数文字のうち,どれか1つが当てはまる箇所を探す時に使える.
一文字一文字Indexを呼び出すよりもシンプルに書ける.
12. IndexByte
シグネチャ
func IndexByte(s string, c byte) int
説明
文字列sの中で,バイト値cが最初に見つかった位置を返す.見つからなければ-1だ.小文字やASCII文字の検索に特化しているケースで高速に処理できる.
使用例
s := "Gopher"
fmt.Println(strings.IndexByte(s, 'p')) // 3
fmt.Println(strings.IndexByte(s, 'z')) // -1
ASCII文字しか出現しない前提(例えば英数字のみなど)であれば,高速化を期待できる.
マルチバイト文字には対応していないため注意が必要.
13. IndexFunc
シグネチャ
func IndexFunc(s string, f func(rune) bool) int
説明
判定関数fがtrueを返す最初のルーンの位置を返す.見つからなければ-1だ.
使用例
s := "Gopher123"
isDigit := func(r rune) bool {
return r >= '0' && r <= '9'
}
fmt.Println(strings.IndexFunc(s, isDigit)) // 6
数字や特定のカテゴリーの文字が最初に出現する場所を探すときなどに便利.
Unicodeの範囲チェックを実装することで,日本語や他の言語文字に対しても柔軟に処理できる.
14. IndexRune
シグネチャ
func IndexRune(s string, r rune) int
説明
文字列s
の中でルーンr
が最初に見つかった位置を返す.見つからなければ-1を返す.IndexByteのルーン対応版と考えられる.
使用例
s := "こんにちは"
fmt.Println(strings.IndexRune(s, 'に')) // 6 (マルチバイト文字の先頭バイト位置)
日本語や絵文字などマルチバイト文字を検索するときに有効.
runeで扱うため,IndexByteでは不可能な検索を簡単に行える.
15. Join
シグネチャ
func Join(elems []string, sep string) string
説明
文字列スライスelems
の要素をsep
で連結して1つの文字列を生成する.
使用例
fruits := []string{"apple", "banana", "orange"}
joined := strings.Join(fruits, ", ")
fmt.Println(joined) // "apple, banana, orange"
スライスに格納された文字列をCSV形式や改行区切りで一括出力したいときに多用される.
要素数が大きい場合でもstrings.Builderを自前で使うよりシンプルに書きやすい.
16. LastIndex
シグネチャ
func LastIndex(s, substr string) int
説明
s
の中でsubstr
が最後に見つかった位置を返す.見つからなければ-1だ.
使用例
s := "banana"
fmt.Println(strings.LastIndex(s, "na")) // 4
最初の出現位置を探すIndexに対して,最後の出現位置を探すのがLastIndex.
重複に配慮しながら最終部分を切り出す際などに使われる.
17. LastIndexAny
シグネチャ
func LastIndexAny(s, chars string) int
説明
s
の中でchars
に含まれるいずれかの文字が最後に出現した位置を返す.見つからなければ-1を返す.
使用例
s := "Gopher!"
fmt.Println(strings.LastIndexAny(s, "!?")) // 6
fmt.Println(strings.LastIndexAny(s, "?")) // -1
複数文字のどれかが出現する最後の位置を一度の関数呼び出しでチェックできる.
パターンが増えるほどIndexAny/LastIndexAnyの恩恵が大きくなる.
18. LastIndexByte
シグネチャ
func LastIndexByte(s string, c byte) int
説明
s
の中でバイト値cが最後に見つかった位置を返す.見つからなければ-1だ.
使用例
s := "Gopher"
fmt.Println(strings.LastIndexByte(s, 'e')) // 4
fmt.Println(strings.LastIndexByte(s, 'x')) // -1
IndexByte同様,ASCII文字を対象とする場面で便利だ.
マルチバイト文字は検索対象外として割り切りたいときは高速かつシンプル.
19. LastIndexFunc
シグネチャ
func LastIndexFunc(s string, f func(rune) bool) int
説明
判定関数fがtrueを返すルーンが最後に出現した位置を返す.見つからなければ-1を返す.
使用例
s := "123Go"
isLetter := func(r rune) bool {
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
}
fmt.Println(strings.LastIndexFunc(s, isLetter)) // 4 ('o')
数字やアルファベットといった文字種の中で最後に出現する場所を探したい場合など,非常に柔軟に使える.
独自のルールに従い,複数文字のカテゴリーをまとめて判断したい場合にも有効.
20. Map
シグネチャ
func Map(mapping func(rune) rune, s string) string
説明
rune
単位でs
を走査し,mapping関数で変換した結果の文字列を返す.
使用例
s := "Hello"
toStar := func(r rune) rune {
if r == 'l' {
return '*'
}
return r
}
fmt.Println(strings.Map(toStar, s)) // "He**o"
- 文字列変換を人力でループするよりも簡潔に書ける.
- ASCIIに限定しない柔軟な文字変換が可能.
21. Repeat
シグネチャ
func Repeat(s string, count int) string
説明
s
をcoun
t回繰り返した文字列を返すだ.
使用例
fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"
繰り返し文字列を生成するときに重宝する.
countに負の値を入れることはできない (ランタイムパニックになる).
22. Replace / ReplaceAll
シグネチャ
- Replace:
func Replace(s, old, new string, n int) string
- ReplaceAll:
func ReplaceAll(s, old, new string) string
説明
- Replaceは
s
内のold
を最大n
回new
に置換する (nが-1の場合はすべて置換) - ReplaceAllは
s
内のold
をすべてnew
に置換する (Replaceのn=-1と同等)
使用例
s := "banana"
fmt.Println(strings.Replace(s, "a", "*", 2)) // "b*nana"
fmt.Println(strings.Replace(s, "a", "*", -1)) // "b*n*n*"
fmt.Println(strings.ReplaceAll(s, "na", "_")) // "ba_a"
部分文字列を置換する基本手段で,正確に指定した回数だけ置換できるという利点がある.
複雑なパターンマッチングが必要な場合はregexpパッケージも検討するとよい.
23. Split, SplitN, SplitAfter, SplitAfterN
シグネチャ
- Split:
func Split(s, sep string) []string
- SplitN:
func SplitN(s, sep string, n int) []string
- SplitAfter:
func SplitAfter(s, sep string) []string
- SplitAfterN:
func SplitAfterN(s, sep string, n int) []string
説明
- Splitは
s
をsep
で分割し,分割された文字列スライスを返す. - SplitNは分割回数の上限
n
を指定できる.n
まで区切った後は残りをまとめて1要素にする. - SplitAfterは,
sep
を含めて分割し,区切り文字の直後で区切る. - SplitAfterNは,
sep
を含めて分割しつつ分割回数の上限n
を指定できる.
使用例
s := "a,b,c"
fmt.Println(strings.Split(s, ",")) // ["a" "b" "c"]
fmt.Println(strings.SplitN(s, ",", 2)) // ["a" "b,c"]
fmt.Println(strings.SplitAfter(s, ",")) // ["a," "b," "c"]
fmt.Println(strings.SplitAfterN(s, ",", 2))// ["a," "b,c"]
CSVのように単純な区切り文字を使う場合はSplitがよく使われる.
分割数の上限を設定したい場合や区切り文字を含めたい場合にNやAfter付きのバリエーションを使う.
24. ToLower, ToUpper, ToTitle
シグネチャ
- ToLower:
func ToLower(s string) string
- ToUpper:
func ToUpper(s string) string
- ToTitle:
func ToTitle(s string) string
説明
- ToLowerは
s
をすべて小文字に変換して返す. - ToUpperは
s
をすべて大文字に変換して返す. - ToTitleはタイトルケース(単語の頭文字を大文字にする等)に変換して返すが,英語以外の言語では挙動が異なる場合がある.
使用例
s := "GoLang"
fmt.Println(strings.ToLower(s)) // "golang"
fmt.Println(strings.ToUpper(s)) // "GOLANG"
fmt.Println(strings.ToTitle(s)) // "GOLANG" (英語ではToUpperと同等の結果になることが多い)
ポイント
大文字小文字を無視して比較やソートをしたいときに前処理としてよく使われる.
Unicode文字列への対応も考慮されているが,複数言語のタイトルケースは複雑であるため要検討.
25. Trim, TrimLeft, TrimRight, TrimSpace, TrimPrefix, TrimSuffix, TrimFunc
シグネチャ
- Trim:
func Trim(s string, cutset string) string
- TrimLeft:
func TrimLeft(s string, cutset string) string
- TrimRight:
func TrimRight(s string, cutset string) string
- TrimSpace:
func TrimSpace(s string) string
- TrimPrefix:
func TrimPrefix(s, prefix string) string
- TrimSuffix:
func TrimSuffix(s, suffix string) string
- TrimFunc:
func TrimFunc(s string, f func(rune) bool) string
説明
- Trimは,
s
の先頭と末尾からcutset
に含まれる文字をすべて取り除く. - TrimLeftとTrimRightは,それぞれ先頭・末尾について同様に取り除く.
- TrimSpaceは,Unicodeで定義されているホワイトスペースを先頭と末尾から除去する.
- TrimPrefixは
s
がprefix
で始まる場合に,その部分を取り除いて返す. - TrimSuffixは
s
がsuffix
で終わる場合に,その部分を取り除いて返す. - TrimFuncは判定関数
f
がtrueを返す文字を先頭と末尾から取り除いて返す.
使用例
s := " hello, world!! "
fmt.Println(strings.Trim(s, " !")) // "hello, world"
fmt.Println(strings.TrimSpace(s)) // "hello, world!!"
fmt.Println(strings.TrimPrefix(s, " h")) // "ello, world!! "
fmt.Println(strings.TrimSuffix(s, "!! ")) // " hello, world"
文字列の前後にある不要な文字や空白を取り除く時に頻繁に使われる.
TrimFuncを使うと,カスタマイズした判定でトリミングが可能だ.
代表的ユースケース例
stringsパッケージの関数を応用して,テキストを整形し単語数をカウントするサンプルコードを示す.コード例では三重バッククオートを使わずにインデントでコードブロックを表現する.
package main
import (
"fmt"
"strings"
)
func main() {
text := " Go is a fast, statically typed, compiled language. Go is fun! "
// 前後の不要な空白を除去
trimmed := strings.TrimSpace(text)
// 句読点や記号を別文字に置換
clean := strings.ReplaceAll(trimmed, ",", "")
clean = strings.ReplaceAll(clean, ".", "")
clean = strings.ReplaceAll(clean, "!", "")
// 空白で分割(細かい制御が必要な場合はFieldsFuncなどを検討する)
words := strings.Fields(clean)
// 単語の出現回数をマップに格納
counts := make(map[string]int)
for _, w := range words {
counts[w]++
}
fmt.Println("整形後の文字列:", clean)
fmt.Println("単語のスライス:", words)
fmt.Println("単語の出現回数:")
for k, v := range counts {
fmt.Printf(" %s: %d\n", k, v)
}
}
このように,stringsパッケージを活用すれば複雑な文字列処理もシンプルにまとめることができる.
Go言語のstringsパッケージは,文字列の探索,変換,分割,置換などの基本的な操作を幅広くカバーしている.各関数の特性を理解し,使いどころを適切に押さえておけば,自前のループ処理や正規表現を多用せずとも十分に効率的で可読性の高いコードを書ける.特にマルチバイト文字の扱い(IndexRune, ContainsRuneなど)や,区切り文字を柔軟に扱うFieldsFuncのような機能は,実務でも便利に使われるであろう