1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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

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

https://pkg.go.dev/strings

シリーズ 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

説明
大文字・小文字を区別せずにstが等しいかどうかを判定する.たとえばGoLanggolangのようにケースが異なる文字列に対しても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

説明
文字列sprefixで始まっていれば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

説明
文字列ssuffixで終わっていれば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

説明
scount回繰り返した文字列を返すだ.

使用例

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を最大nnewに置換する (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はssepで分割し,分割された文字列スライスを返す.
  • 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はsprefixで始まる場合に,その部分を取り除いて返す.
  • TrimSuffixはssuffixで終わる場合に,その部分を取り除いて返す.
  • 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のような機能は,実務でも便利に使われるであろう

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?