Project Gopher: Unlocking Go’s Secrets Part2 [標準ライブラリstringパッケージの使い方を徹底解剖]

何かプロダクトを作成する際に文字列を操作したり，検索したり，といった処理が必要になることはよくあります．Go言語の標準ライブラリであるstringsパッケージには，文字列操作を効率よく行うための関数が数多く用意されている．この記事では，主要な関数ごとに仕様や使用例を詳しく解説する．

:::note warn
Goのバージョンによっては一部の関数の挙動や追加有無が変化することがあるため，使用時には公式ドキュメントを参照するとよい．

https://pkg.go.dev/strings

:::

# シリーズ Project Gopher: Unlocking Go’s Secrets
**Part1** context.Contextってなんなんだ

https://qiita.com/JavaLangRuntimeException/items/dc45b412d3fbd2ccb9e8

# 他のシリーズ記事
Goを知らない人は以下の記事から．

https://qiita.com/JavaLangRuntimeException/items/d388717fc1436bc3ec9d

> 上の記事も〇〇チートシートとしてシリーズ化しているのでぜひご覧ください．様々な言語，フレームワーク，ライブラリなど開発技術の使用方法，基本事項，応用事例を網羅し，手引書として記載したシリーズです．
git/gh，lazygit，docker，vim，typescript，SQL，プルリクエスト/マークダウン，ステータスコード，ファイル操作，OpenAI AssistantsAPI，Ruby/Ruby on Rails のチートシートがあります．以下の記事に遷移した後，各種チートシートのリンクがあります.

**Gopher’s Journey: Exploring TCP Protocol**
Goを用いてTCPプロトコルを探求 & 作成するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/38091220106d86651d2b

**TypeScriptで学ぶプログラミングの世界**
プログラミング言語を根本的に理解するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/cadf49bb419076819963

**情報処理技術者試験合格への道 [IP・SG・FE・AP]**
情報処理技術者試験に出題されるコンピュータサイエンス用語の紹介や単語集

https://qiita.com/JavaLangRuntimeException/items/991be402099542ccb936

**IAM AWS User クラウドサービスをフル活用しよう！**
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/371a334f5a6e07035db5

**AWS UserのGCP浮気日記**
GCPの様子をAWSと比較して考えてみるシリーズ

https://qiita.com/JavaLangRuntimeException/items/527d99e774165a763180


# インポート方法

まずはstringsパッケージをインポートする．標準ライブラリなので以下のようにimportするだけで使える．
```go
import (
    "fmt"
    "strings"
)
```
以下では，頻繁に使用される関数を1つずつ取り上げ，シグネチャ，説明，使用例，具体的な注意点やテクニックなどを詳しく紹介する．

# 1. Contains

**シグネチャ**
```go
func Contains(s, substr string) bool
```
**説明**
sの中にsubstrが含まれていればtrueを返し，含まれていなければfalseを返す．調べたい文字列に特定の部分文字列が存在するかどうかを確かめるのに便利だ．

**使用例**
```go
s := "Go is awesome!"
fmt.Println(strings.Contains(s, "some")) // true
fmt.Println(strings.Contains(s, "SoMe")) // false (大文字小文字は区別される)
```
大文字と小文字は区別されるため，ケースを無視して判定したいときには[strings.EqualFold](#5-equalfold)などを併用するか，一旦すべて小文字または大文字に変換してからContainsを使うという手がある．

# 2. ContainsAny

**シグネチャ**
```go
func ContainsAny(s, chars string) bool
```
**説明**
文字列sにchars中のいずれか1文字でも含まれていればtrueを返す

**使用例**
```go
s := "Gopher"
fmt.Println(strings.ContainsAny(s, "xyz"))     // false
fmt.Println(strings.ContainsAny(s, "abcPh"))   // true ('h'が含まれている)
```
複数の文字のいずれか1つでも含まれているかを手軽に判定できる．
逆に，特定の文字群以外から成り立っているかどうかをチェックしたい場合は，ループや正規表現を活用することも考慮するとよい．

# 3. ContainsRune

**シグネチャ**
```go
func ContainsRune(s string, r rune) bool
```
**説明**
文字列`s`の中に，指定したルーン`r`が含まれていればtrueを返す．文字列がマルチバイト文字を含む場合でも正しく判定する．

**使用例**
```go
s := "こんにちは"
fmt.Println(strings.ContainsRune(s, 'に')) // true
fmt.Println(strings.ContainsRune(s, 'あ')) // false
```
文字列をルーン単位で判定できる．
Go言語では，文字＝runeという扱い方をする場合が多いため，ContainsRuneはマルチバイト文字の有無を判別するのに便利だ．


# 4. Count

**シグネチャ**
```go
func Count(s, sep string) int
```
**説明**
文字列sの中で，`sep`が出現する回数を返す．重複している場合も数え上げられる．

**使用例**
```go
s := "banana"
fmt.Println(strings.Count(s, "a"))  // 3
fmt.Println(strings.Count(s, "na")) // 2
```
空文字列("")をsepに指定すると特別な動作があり，全ての間に空文字列が存在するとみなしてs.Len()+1を返すことに注意．
主に単純な部分文字列が何回出現するかを調べたい時に使う．

# 5. EqualFold

**シグネチャ**
```go
func EqualFold(s, t string) bool
```
**説明**
大文字・小文字を区別せずに`s`と`t`が等しいかどうかを判定する．たとえば`GoLang`と`golang`のようにケースが異なる文字列に対してもtrueを返す．

**使用例**
```go
fmt.Println(strings.EqualFold("GoLang", "golang")) // true
fmt.Println(strings.EqualFold("GoLang", "Golang!")) // false
```
完全にケースを無視して文字列を比較したい時に使うと便利だ．
Unicodeのケースに対しても対応しているため，よりグローバルな需要にも応えられる．


# 6. Fields

**シグネチャ**
```go
func Fields(s string) []string
```
**説明**
連続したホワイトスペースを区切り文字として，文字列sを分割し，分割後のスライスを返すだ．

**使用例**
```go
s := "  Go is an   open-source   language "
fmt.Println(strings.Fields(s))
// ["Go", "is", "an", "open-source", "language"]
```
Fieldsは空白文字（タブや改行も含む）を全て区切りとして扱う．
CSVのように特定の文字で分割したい場合にはSplitやSplitNを使う．

# 7. FieldsFunc

**シグネチャ**
```go
func FieldsFunc(s string, f func(rune) bool) []string
```
**説明**
判定関数fがtrueを返す文字を区切りとして文字列を分割し，結果のスライスを返す．より柔軟な分割ロジックを組める．

**使用例**
```go
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

**シグネチャ**
```go
func HasPrefix(s, prefix string) bool
```
**説明**
文字列`s`が`prefix`で始まっていればtrueを返す．先頭の部分一致を素早くチェックできる．

**使用例**
```go
s := "golang"
fmt.Println(strings.HasPrefix(s, "go"))   // true
fmt.Println(strings.HasPrefix(s, "lang")) // false
```
ファイルパスの拡張子チェックや，URLのプロトコルチェックに使える．
大文字小文字の違いは区別されるため，ケースを無視する場合は別途ToLower等が必要．

# 9. HasSuffix

**シグネチャ**
```go
func HasSuffix(s, suffix string) bool
```
**説明**
文字列`s`が`suffix`で終わっていればtrueを返す．

**使用例**
```go
s := "golang"
fmt.Println(strings.HasSuffix(s, "lang")) // true
fmt.Println(strings.HasSuffix(s, "go"))   // false
```
ファイル名の拡張子チェックなどによく使われる．
こちらも大文字小文字は区別される．

# 10. Index

**シグネチャ**
```go
func Index(s, substr string) int
```
**説明**
`s`の中で`substr`が最初に見つかった位置（インデックス）を返す．文字が見つからない場合は-1を返す．

**使用例**
```go
s := "Hello Gopher"
fmt.Println(strings.Index(s, "o"))   // 4
fmt.Println(strings.Index(s, "xyz")) // -1
```
利用例としては，特定の文字列が最初に現れる位置を調べて，後続の文字を切り出すなどの処理が挙げられる．
最初ではなく最後に見つかった位置を探したい場合はLastIndexを使う．

# 11. IndexAny

**シグネチャ**
```go
func IndexAny(s, chars string) int
```
**説明**
sの中でchars中のいずれかの文字が最初に出現した位置を返す．見つからなければ-1を返す．

**使用例**
```go
s := "Gopher"
fmt.Println(strings.IndexAny(s, "abcPh")) // 1 ('o'が該当)
fmt.Println(strings.IndexAny(s, "xyz"))   // -1
```
複数文字のうち，どれか1つが当てはまる箇所を探す時に使える．
一文字一文字Indexを呼び出すよりもシンプルに書ける．

# 12. IndexByte

**シグネチャ**
```go
func IndexByte(s string, c byte) int
```
**説明**
文字列sの中で，バイト値cが最初に見つかった位置を返す．見つからなければ-1だ．小文字やASCII文字の検索に特化しているケースで高速に処理できる．

**使用例**
```go
s := "Gopher"
fmt.Println(strings.IndexByte(s, 'p')) // 3
fmt.Println(strings.IndexByte(s, 'z')) // -1
```
ASCII文字しか出現しない前提(例えば英数字のみなど)であれば，高速化を期待できる．
マルチバイト文字には対応していないため注意が必要．

# 13. IndexFunc

**シグネチャ**
```go
func IndexFunc(s string, f func(rune) bool) int
```
**説明**
判定関数fがtrueを返す最初のルーンの位置を返す．見つからなければ-1だ．

**使用例**
```go
s := "Gopher123"
isDigit := func(r rune) bool {
    return r >= '0' && r <= '9'
}
fmt.Println(strings.IndexFunc(s, isDigit)) // 6
```
数字や特定のカテゴリーの文字が最初に出現する場所を探すときなどに便利．
Unicodeの範囲チェックを実装することで，日本語や他の言語文字に対しても柔軟に処理できる．

# 14. IndexRune

**シグネチャ**
```go
func IndexRune(s string, r rune) int
```
**説明**
文字列`s`の中でルーン`r`が最初に見つかった位置を返す．見つからなければ-1を返す．IndexByteのルーン対応版と考えられる．

**使用例**
```go
s := "こんにちは"
fmt.Println(strings.IndexRune(s, 'に')) // 6 (マルチバイト文字の先頭バイト位置)
```
日本語や絵文字などマルチバイト文字を検索するときに有効．
runeで扱うため，IndexByteでは不可能な検索を簡単に行える．

# 15. Join

**シグネチャ**
```go
func Join(elems []string, sep string) string
```
**説明**
文字列スライス`elems`の要素を`sep`で連結して1つの文字列を生成する．

**使用例**
```go
fruits := []string{"apple", "banana", "orange"}
joined := strings.Join(fruits, ", ")
fmt.Println(joined) // "apple, banana, orange"
```
スライスに格納された文字列をCSV形式や改行区切りで一括出力したいときに多用される．
要素数が大きい場合でもstrings.Builderを自前で使うよりシンプルに書きやすい．

# 16. LastIndex

**シグネチャ**
```go
func LastIndex(s, substr string) int
```
**説明**
`s`の中で`substr`が最後に見つかった位置を返す．見つからなければ-1だ．

**使用例**
```go
s := "banana"
fmt.Println(strings.LastIndex(s, "na")) // 4
```
*最初*の出現位置を探すIndexに対して，*最後*の出現位置を探すのがLastIndex．
重複に配慮しながら最終部分を切り出す際などに使われる．

# 17. LastIndexAny

**シグネチャ**
```go
func LastIndexAny(s, chars string) int
```
**説明**
`s`の中で`chars`に含まれるいずれかの文字が最後に出現した位置を返す．見つからなければ-1を返す．

**使用例**
```go
s := "Gopher!"
fmt.Println(strings.LastIndexAny(s, "!?")) // 6
fmt.Println(strings.LastIndexAny(s, "?"))  // -1
```
複数文字のどれかが出現する最後の位置を一度の関数呼び出しでチェックできる．
パターンが増えるほどIndexAny/LastIndexAnyの恩恵が大きくなる．

---

# 18. LastIndexByte

**シグネチャ**
```go
func LastIndexByte(s string, c byte) int
```
**説明**
`s`の中でバイト値cが最後に見つかった位置を返す．見つからなければ-1だ．

**使用例**
```go
s := "Gopher"
fmt.Println(strings.LastIndexByte(s, 'e')) // 4
fmt.Println(strings.LastIndexByte(s, 'x')) // -1
```
IndexByte同様，ASCII文字を対象とする場面で便利だ．
マルチバイト文字は検索対象外として割り切りたいときは高速かつシンプル．

---

# 19. LastIndexFunc

**シグネチャ**
```go
func LastIndexFunc(s string, f func(rune) bool) int
```
**説明**
判定関数fがtrueを返すルーンが最後に出現した位置を返す．見つからなければ-1を返す．

**使用例**
```go
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

**シグネチャ**
```go
func Map(mapping func(rune) rune, s string) string
```
**説明**
`rune`単位で`s`を走査し，mapping関数で変換した結果の文字列を返す．

**使用例**
```go
s := "Hello"
toStar := func(r rune) rune {
    if r == 'l' {
        return '*'
    }
    return r
}
fmt.Println(strings.Map(toStar, s))  // "He**o"
```
- 文字列変換を人力でループするよりも簡潔に書ける．
- ASCIIに限定しない柔軟な文字変換が可能．

---

# 21. Repeat

**シグネチャ**
```go
func Repeat(s string, count int) string
```
**説明**
`s`を`coun`t回繰り返した文字列を返すだ．

**使用例**
```go
fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"
```
繰り返し文字列を生成するときに重宝する．
countに負の値を入れることはできない (ランタイムパニックになる)．

# 22. Replace / ReplaceAll

**シグネチャ**

- Replace:
```go
func Replace(s, old, new string, n int) string
```
- ReplaceAll:
```go
func ReplaceAll(s, old, new string) string
```
**説明**
- Replaceは`s`内の`old`を最大`n`回`new`に置換する (nが-1の場合はすべて置換)
- ReplaceAllは`s`内の`old`をすべて`new`に置換する (Replaceのn=-1と同等)

**使用例**
```go
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:
```go
func Split(s, sep string) []string
```
- SplitN:
```go
func SplitN(s, sep string, n int) []string
```
- SplitAfter:
```go
func SplitAfter(s, sep string) []string
```
- SplitAfterN:
```go
func SplitAfterN(s, sep string, n int) []string
```
**説明**
- Splitは`s`を`sep`で分割し，分割された文字列スライスを返す．
- SplitNは分割回数の上限`n`を指定できる．`n`まで区切った後は残りをまとめて1要素にする．
- SplitAfterは，`sep`を含めて分割し，区切り文字の直後で区切る．
- SplitAfterNは，`sep`を含めて分割しつつ分割回数の上限`n`を指定できる．

**使用例**
```go
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:
```go
func ToLower(s string) string
```
- ToUpper:
```go
func ToUpper(s string) string
```
- ToTitle:
```go
func ToTitle(s string) string
```
**説明**
- ToLowerは`s`をすべて小文字に変換して返す．
- ToUpperは`s`をすべて大文字に変換して返す．
- ToTitleはタイトルケース(単語の頭文字を大文字にする等)に変換して返すが，英語以外の言語では挙動が異なる場合がある．

**使用例**
```go
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:
```go
func Trim(s string, cutset string) string
```
- TrimLeft:
```go
func TrimLeft(s string, cutset string) string
```
- TrimRight:
```go
func TrimRight(s string, cutset string) string
```
- TrimSpace:
```go
func TrimSpace(s string) string
```
- TrimPrefix:
```go
func TrimPrefix(s, prefix string) string
```
- TrimSuffix:
```go
func TrimSuffix(s, suffix string) string
```
- TrimFunc:
```go
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を返す文字を先頭と末尾から取り除いて返す．

**使用例**
```go
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パッケージの関数を応用して，テキストを整形し単語数をカウントするサンプルコードを示す．コード例では三重バッククオートを使わずにインデントでコードブロックを表現する．
```go
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のような機能は，実務でも便利に使われるであろう