文字列処理
レッスン19:文字列処理
🎯 生活での例え
あなたが図書館の司書であると想像してください。毎日大量のテキスト作業を処理しています:
- 検索:書籍のタイトルに特定のキーワードが含まれているか確認する →
strings.Contains - 置換:古いラベルを新しいものに置き換える →
strings.Replace - 分割:カンマ区切りのタグリストを分割する →
strings.Split - 結合:複数のキーワードを検索クエリに結合する →
strings.Join - トリム:書籍タイトルの先頭と末尾の余分なスペースを削除する →
strings.Trim
文字列処理はプログラムにおける「テキスト作業」です。ほぼすべてのプログラムがテキストを扱う必要があります。
コアコンセプト
Goは文字列を処理するためのいくつかの標準ライブラリを提供しています:
| パッケージ | 用途 | 主な関数 |
|---|---|---|
strings |
文字列の検索、置換、分割、結合など | Contains, Replace, Split, Join, Trim |
strconv |
文字列と他の型間の変換 | Atoi, Itoa, ParseBool, FormatFloat |
unicode/utf8 |
UTF-8エンコーディング関連の操作 | RuneCountInString, ValidString |
strings.Builder |
多数の文字列の効率的な連結 | WriteString, String |
重要なポイント:
- Goの文字列は不変です — 修正するたびに新しい文字列が作成されます
- Goの文字列は底层ではUTF-8エンコードされたバイト列です
len(str)はバイト数を返します。文字数ではありませんruneはGoのUnicodeコードポイントを表現する型です(本质上はint32)
基本構文と使い方
1. stringsパッケージ
GO
package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, Go语言!"
// 検索
fmt.Println(strings.Contains(str, "Go")) // true
fmt.Println(strings.HasPrefix(str, "Hello")) // true
fmt.Println(strings.HasSuffix(str, "!")) // true
fmt.Println(strings.Index(str, "Go")) // 7
// 置換
result := strings.Replace(str, "Go", "Golang", 1)
fmt.Println(result) // "Hello, Golang语言!"
// 全体置換
s := "aabbcc"
fmt.Println(strings.ReplaceAll(s, "a", "x")) // "xxbbcc"
// 分割と結合
csv := "apple,banana,cherry"
fruits := strings.Split(csv, ",")
fmt.Println(fruits) // [apple banana cherry]
joined := strings.Join(fruits, " | ")
fmt.Println(joined) // "apple | banana | cherry"
// トリム
padded := " Hello World "
fmt.Println(strings.TrimSpace(padded)) // "Hello World"
fmt.Println(strings.Trim("##Hello##", "#")) // "Hello"
fmt.Println(strings.TrimLeft("##Hello##", "#")) // "Hello##"
// 大文字小文字変換
fmt.Println(strings.ToUpper("hello")) // "HELLO"
fmt.Println(strings.ToLower("HELLO")) // "hello"
// 繰り返し
fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"
// カウント
fmt.Println(strings.Count("banana", "an")) // 2
}
💡 ヒント:
strings.Splitの区切り文字が空文字列の場合、文字列は個々の文字のスライスに分割されます。
2. strconvパッケージ
GO
package main
import (
"fmt"
"strconv"
)
func main() {
// 文字列 → 整数
num, err := strconv.Atoi("42")
if err != nil {
fmt.Println("変換失敗:", err)
}
fmt.Println(num) // 42
// 整数 → 文字列
str := strconv.Itoa(42)
fmt.Println(str) // "42"
// 文字列 → ブール値
b, err := strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>
// 文字列 → 浮動小数点数
f, err := strconv.ParseFloat("3.14", 64)
fmt.Println(f, err) // 3.14 <nil>
// 浮動小数点数 → 文字列
// 'f'は通常フォーマット、-1は最小桁数、64はfloat64を意味します
s := strconv.FormatFloat(3.14, 'f', -1, 64)
fmt.Println(s) // "3.14"
// フォーマット出力(C言語のsprintfに類似)
formatted := strconv.FormatInt(255, 16) // 16進数
fmt.Println(formatted) // "ff"
}
💡 ヒント:
strconv.Atoiはstrconv.ParseInt(s, 10, 0)と等価で、プラットフォーム依存のint型を返します。
3. unicode/utf8パッケージ
GO
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Go语言编程"
// len()はバイト数を返します
fmt.Println(len(str)) // 14(中国語の各文字は3バイト)
// utf8.RuneCountInString()は文字数を返します
fmt.Println(utf8.RuneCountInString(str)) // 7
// 有効なUTF-8かチェック
fmt.Println(utf8.ValidString(str)) // true
fmt.Println(utf8.ValidString("abc")) // true
// 文字列の各runeを繰り返し処理
for i, r := range str {
fmt.Printf("インデックス:%d 文字:%c Unicode:%U\n", i, r, r)
}
}
💡 ヒント:
rangeで文字列を繰り返し処理する際、Goは自動的にバイト単位ではなくrune(Unicode文字)単位で繰り返します。
4. strings.Builder(効率的な連結)
GO
package main
import (
"fmt"
"strings"
)
func main() {
// ❌ 非効率:連結のたびに新しい文字列が作成される
// result := ""
// for i := 0; i < 1000; i++ {
// result += "a" // 毎回新しいメモリを割り当てる
// }
// ✅ 効率的:strings.Builderを使用
var builder strings.Builder
for i := 0; i < 1000; i++ {
builder.WriteString("a")
}
result := builder.String()
fmt.Println(len(result)) // 1000
// 事前割り当てでさらにパフォーマンス向上
var builder2 strings.Builder
builder2.Grow(1000) // 1000バイトを事前割り当て
for i := 0; i < 1000; i++ {
builder2.WriteString("b")
}
fmt.Println(builder2.Len()) // 1000
}
💡 ヒント:
strings.Builderは内部的に[]byteスライスを使用し、文字列の不変性による頻繁なメモリ割り当てを回避します。
例題コード
例:文字列の統計と分析(難易度⭐)
GO
package main
import (
"fmt"
"strings"
"unicode"
)
// 文字列内の異なる文字タイプのカウントを分析
func analyzeString(s string) (letters, digits, spaces, others int) {
for _, r := range s {
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
case unicode.IsSpace(r):
spaces++
default:
others++
}
}
return
}
func main() {
text := "Hello, Go语言! 2024年 Version 2.0"
letters, digits, spaces, others := analyzeString(text)
fmt.Printf("テキスト: %q\n", text)
fmt.Printf("文字数: %d\n", letters)
fmt.Printf("数字: %d\n", digits)
fmt.Printf("スペース: %d\n", spaces)
fmt.Printf("その他: %d\n", others)
// 単語頻度をカウント
words := strings.Fields("the go the language the world")
freq := make(map[string]int)
for _, w := range words {
freq[strings.ToLower(w)]++
}
fmt.Println("\n単語頻度:", freq)
}
出力:
TEXT
テキスト: "Hello, Go语言! 2024年 Version 2.0"
文字数: 17
数字: 6
スペース: 5
その他: 3
単語頻度: map[go:1 language:1 the:3 world:1]
例:CSVパーサー(難易度⭐⭐)
GO
package main
import (
"fmt"
"strings"
)
// 引用符付きフィールドをサポートするシンプルなCSV行パーサー
func parseCSVLine(line string) []string {
var fields []string
var current strings.Builder
inQuotes := false
for _, r := range line {
switch {
case r == '"' && !inQuotes:
// 引用符領域に入る
inQuotes = true
case r == '"' && inQuotes:
// 引用符領域を出る
inQuotes = false
case r == ',' && !inQuotes:
// 区切り文字に到達、現在のフィールドを保存
fields = append(fields, current.String())
current.Reset()
default:
// 通常の文字
current.WriteRune(r)
}
}
// 最後のフィールドを保存
fields = append(fields, current.String())
return fields
}
// フィールドをクリーンアップしてフォーマット
func cleanFields(fields []string) []string {
cleaned := make([]string, len(fields))
for i, f := range fields {
cleaned[i] = strings.TrimSpace(f)
}
return cleaned
}
func main() {
// シミュレーション用CSVデータ
csvData := []string{
`Alice,28,"Beijing, China"`,
`Bob,35,"New York, USA"`,
`Charlie,42,"London, UK"`,
}
fmt.Println("=== CSV解析結果 ===")
for _, line := range csvData {
fields := parseCSVLine(line)
fields = cleanFields(fields)
fmt.Printf("名前: %-10s 年齢: %-4s 場所: %s\n",
fields[0], fields[1], fields[2])
}
// 逆操作:スライスをCSV行に結合
record := []string{"David", "30", "Shanghai, China"}
csvLine := strings.Join(record, ",")
fmt.Println("\n生成されたCSV行:", csvLine)
}
出力:
TEXT
=== CSV解析結果 ===
名前: Alice 年齢: 28 場所: Beijing, China
名前: Bob 年齢: 35 場所: New York, USA
名前: Charlie 年齢: 42 場所: London, UK
生成されたCSV行: David,30,Shanghai, China
例:テンプレートエンジン(難易度⭐⭐⭐)
GO
package main
import (
"fmt"
"strconv"
"strings"
)
// シンプルなテンプレートエンジン:{{key}}を対応する値に置換
func renderTemplate(template string, data map[string]string) string {
var result strings.Builder
result.Grow(len(template) * 2) // 容量を推定
i := 0
for i < len(template) {
// "{{"を探す
if i+1 < len(template) && template[i] == '{' && template[i+1] == '{' {
// 対応する "}}"を探す
end := strings.Index(template[i+2:], "}}")
if end != -1 {
key := strings.TrimSpace(template[i+2 : i+2+end])
if value, ok := data[key]; ok {
result.WriteString(value)
} else {
// キーが見つからない場合はそのまま保持
result.WriteString("{{" + key + "}}")
}
i += end + 4 // "}}"をスキップ
continue
}
}
result.WriteByte(template[i])
i++
}
return result.String()
}
// テーブル出力をフォーマット
func formatTable(headers []string, rows [][]string) string {
// 各列の最大幅を計算
colWidths := make([]int, len(headers))
for i, h := range headers {
colWidths[i] = len(h)
}
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) && len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
var b strings.Builder
// ヘッダーを書き込み
for i, h := range headers {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], h))
}
b.WriteString("\n")
// 区切り線を書き込み
for i := range headers {
b.WriteString(strings.Repeat("-", colWidths[i]) + "-+-")
}
b.WriteString("\n")
// データ行を書き込み
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], cell))
}
}
b.WriteString("\n")
}
return b.String()
}
// 数値文字列を異なる基数表現に変換
func toBases(numStr string) (map[string]string, error) {
num, err := strconv.ParseInt(numStr, 10, 64)
if err != nil {
return nil, err
}
return map[string]string{
"decimal": strconv.FormatInt(num, 10),
"binary": strconv.FormatInt(num, 2),
"octal": strconv.FormatInt(num, 8),
"hexadecimal": strconv.FormatInt(num, 16),
}, nil
}
func main() {
// 1. テンプレートレンダリング
fmt.Println("=== テンプレートレンダリング ===")
template := "Hello, {{name}}! Welcome to {{city}}. You have {{count}} new messages."
data := map[string]string{
"name": "Alice",
"city": "Beijing",
"count": "5",
}
fmt.Println(renderTemplate(template, data))
// 2. テーブルフォーマット
fmt.Println("\n=== テーブルフォーマット ===")
headers := []string{"Name", "Age", "City"}
rows := [][]string{
{"Alice", "28", "Beijing"},
{"Bob", "35", "New York"},
{"Charlie", "42", "London"},
}
fmt.Print(formatTable(headers, rows))
// 3. 基数変換
fmt.Println("\n=== 基数変換 ===")
bases, _ := toBases("255")
for name, value := range bases {
fmt.Printf("%-12s: %s\n", name, value)
}
}
出力:
TEXT
=== テンプレートレンダリング ===
Hello, Alice! Welcome to Beijing. You have 5 new messages.
=== テーブルフォーマット ===
Name | Age | City |
--------+-----+---------+-
Alice | 28 | Beijing |
Bob | 35 | New York|
Charlie | 42 | London |
=== 基数変換 ===
decimal : 255
binary : 11111111
octal : 377
hexadecimal : ff
実践的な応用シナリオ
シナリオ1:ログアナライザー
GO
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// ログエントリー構造体
type LogEntry struct {
Timestamp string
Level string
Message string
Source string
}
// ログ行を解析
// フォーマット: [2024-01-15 10:30:00] [ERROR] Database connection failed (db-service)
func parseLogLine(line string) (*LogEntry, error) {
entry := &LogEntry{}
// タイムスタンプを抽出
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Timestamp = line[start+1 : end]
line = line[end+1:]
}
}
// ログレベルを抽出
line = strings.TrimSpace(line)
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Level = line[start+1 : end]
line = line[end+1:]
}
}
// メッセージとソースを抽出
line = strings.TrimSpace(line)
if parenStart := strings.LastIndex(line, "("); parenStart != -1 {
if parenEnd := strings.LastIndex(line, ")"); parenEnd != -1 {
entry.Source = line[parenStart+1 : parenEnd]
entry.Message = strings.TrimSpace(line[:parenStart])
}
} else {
entry.Message = line
}
return entry, nil
}
// ログレベルを分析
func analyzeLogs(entries []LogEntry) map[string]int {
stats := make(map[string]int)
for _, e := range entries {
stats[strings.ToUpper(e.Level)]++
}
return stats
}
// キーワードを含むログをフィルタリング
func filterLogs(entries []LogEntry, keyword string) []LogEntry {
var filtered []LogEntry
keyword = strings.ToLower(keyword)
for _, e := range entries {
if strings.Contains(strings.ToLower(e.Message), keyword) {
filtered = append(filtered, e)
}
}
return filtered
}
func main() {
// シミュレーション用ログデータ
logLines := []string{
"[2024-01-15 10:30:00] [INFO] Application started (main-service)",
"[2024-01-15 10:30:05] [INFO] Connected to database (db-service)",
"[2024-01-15 10:31:00] [WARN] High memory usage detected (monitor)",
"[2024-01-15 10:32:00] [ERROR] Database connection timeout (db-service)",
"[2024-01-15 10:32:01] [ERROR] Retry failed, switching to backup (db-service)",
"[2024-01-15 10:33:00] [INFO] Backup database connected (db-service)",
"[2024-01-15 10:35:00] [DEBUG] Cache cleared (cache-service)",
}
// すべてのログを解析
var entries []LogEntry
for _, line := range logLines {
entry, err := parseLogLine(line)
if err == nil {
entries = append(entries, *entry)
}
}
// ログレベル統計
fmt.Println("=== ログレベル統計 ===")
stats := analyzeLogs(entries)
for level, count := range stats {
fmt.Printf(" %s: %d件\n", level, count)
}
// エラーログをフィルタリング
fmt.Println("\n=== エラーログ ===")
for _, e := range entries {
if strings.ToUpper(e.Level) == "ERROR" {
fmt.Printf(" %s | %s | %s\n", e.Timestamp, e.Message, e.Source)
}
}
// キーワードで検索
fmt.Println("\n=== 'database'を含むログ ===")
filtered := filterLogs(entries, "database")
for _, e := range filtered {
fmt.Printf(" [%s] %s\n", e.Level, e.Message)
}
}
出力:
TEXT
=== ログレベル統計 ===
INFO: 3件
WARN: 1件
ERROR: 2件
DEBUG: 1件
=== エラーログ ===
2024-01-15 10:32:00 | Database connection timeout | db-service
2024-01-15 10:32:01 | Retry failed, switching to backup | db-service
=== 'database'を含むログ ===
[INFO] Connected to database
[ERROR] Database connection timeout
[INFO] Backup database connected
シナリオ2:ユーザー入力のバリデーションとサニタイズ
GO
package main
import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
)
// ユーザー名の入力をサニタイズ
func sanitizeUsername(name string) (string, error) {
// 先頭と末尾のスペースを削除
name = strings.TrimSpace(name)
// 長さをチェック
if len(name) < 3 {
return "", fmt.Errorf("ユーザー名が短すぎます(最低3文字)")
}
if len(name) > 20 {
return "", fmt.Errorf("ユーザー名が長すぎます(最大20文字)")
}
// 文字、数字、アンダースコアのみ許可
var cleaned strings.Builder
for _, r := range name {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
cleaned.WriteRune(r)
}
}
result := cleaned.String()
if len(result) < 3 {
return "", fmt.Errorf("有効な文字が少なすぎます")
}
return strings.ToLower(result), nil
}
// 電話番号をバリデーションして解析(中国大陆)
func parsePhone(phone string) (string, error) {
// すべてのスペースとハイフンを削除
phone = strings.ReplaceAll(phone, " ", "")
phone = strings.ReplaceAll(phone, "-", "")
// +86で始まるかチェック
if strings.HasPrefix(phone, "+86") {
phone = phone[3:]
} else if strings.HasPrefix(phone, "86") {
phone = phone[2:]
}
// 長さをバリデーション
if len(phone) != 11 {
return "", fmt.Errorf("電話番号の長さが正しくありません: %d桁", len(phone))
}
// すべて数字かバリデーション
for _, r := range phone {
if !unicode.IsDigit(r) {
return "", fmt.Errorf("電話番号に数字以外の文字が含まれています: %c", r)
}
}
// 1で始まるかバリデーション
if !strings.HasPrefix(phone, "1") {
return "", fmt.Errorf("電話番号は1で始まる必要があります")
}
return phone, nil
}
// 単位付きサイズ文字列を解析
func parseSize(sizeStr string) (int64, error) {
sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr))
// 数値部分と単位部分を抽出
var numPart strings.Builder
var unitPart strings.Builder
for _, r := range sizeStr {
if unicode.IsDigit(r) || r == '.' {
numPart.WriteRune(r)
} else if unicode.IsLetter(r) {
unitPart.WriteRune(r)
}
}
num, err := strconv.ParseFloat(numPart.String(), 64)
if err != nil {
return 0, fmt.Errorf("無効な数値: %s", numPart.String())
}
// 単位に基づいてバイトに変換
unit := unitPart.String()
multipliers := map[string]int64{
"B": 1,
"KB": 1024,
"MB": 1024 * 1024,
"GB": 1024 * 1024 * 1024,
"TB": 1024 * 1024 * 1024 * 1024,
}
multiplier, ok := multipliers[unit]
if !ok {
return 0, fmt.Errorf("不明な単位: %s", unit)
}
return int64(num * float64(multiplier)), nil
}
func main() {
// ユーザー名サニタイズテスト
fmt.Println("=== ユーザー名バリデーション ===")
usernames := []string{" Alice_123 ", "ab", "A!@#B", "GoDeveloper2024"}
for _, u := range usernames {
result, err := sanitizeUsername(u)
if err != nil {
fmt.Printf(" %q → エラー: %v\n", u, err)
} else {
fmt.Printf(" %q → %q\n", u, result)
}
}
// 電話番号解析テスト
fmt.Println("\n=== 電話番号解析 ===")
phones := []string{"138 0013 8000", "+86-138-0013-8000", "12345", "23800138000"}
for _, p := range phones {
result, err := parsePhone(p)
if err != nil {
fmt.Printf(" %q → エラー: %v\n", p, err)
} else {
fmt.Printf(" %q → %s\n", p, result)
}
}
// ファイルサイズ解析テスト
fmt.Println("\n=== ファイルサイズ解析 ===")
sizes := []string{"1.5GB", "512MB", "1024KB", "100B", "2TB"}
for _, s := range sizes {
bytes, err := parseSize(s)
if err != nil {
fmt.Printf(" %s → エラー: %v\n", s, err)
} else {
fmt.Printf(" %s → %dバイト\n", s, bytes)
}
}
}
出力:
TEXT
=== ユーザー名バリデーション ===
" Alice_123 " → "alice_123"
"ab" → エラー: ユーザー名が短すぎます(最低3文字)
"A!@#B" → エラー: 有効な文字が少なすぎます
"GoDeveloper2024" → "godeveloper2024"
=== 電話番号解析 ===
"138 0013 8000" → 13800138000
"+86-138-0013-8000" → 13800138000
"12345" → エラー: 電話番号の長さが正しくありません: 5桁
"23800138000" → エラー: 電話番号は1で始まる必要があります
=== ファイルサイズ解析 ===
1.5GB → 1610612736バイト
512MB → 536870912バイト
1024KB → 1048576バイト
100B → 100バイト
2TB → 2199023255552バイト
❓ よくある質問
質問1:なぜlen("Go语言")は4ではなく8を返すのですか?
GO
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Go语言"
// len()はバイト数を返します。文字数ではありません
fmt.Println("len():", len(s)) // 8
// 中国語の文字はUTF-8で各3バイト
// G(1) + o(1) + 语(3) + 言(3) = 8
// 文字数を取得する正しい方法
fmt.Println("RuneCountInString():", utf8.RuneCountInString(s)) // 4
// またはrangeでカウント
count := 0
for range s {
count++
}
fmt.Println("range count:", count) // 4
}
重要なポイント: 中国語のようなマルチバイト文字を扱う場合は、常にutf8.RuneCountInString()またはrangeを使用して真の文字数を取得してください。
質問2:文字列連結 — +とstrings.Builderのどちらを使うべきですか?
GO
package main
import (
"fmt"
"strings"
)
func main() {
// 少数の連結:+を使用即可(コンパイラが最適化)
s := "Hello" + " " + "World"
fmt.Println(s)
// 多数の連結:strings.Builderを使用すべき
var builder strings.Builder
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
fmt.Println("長さ:", builder.Len())
// 事前割り当てでさらにパフォーマンス向上
var builder2 strings.Builder
builder2.Grow(10000) // 10000バイトを事前割り当て
for i := 0; i < 10000; i++ {
builder2.WriteString("b")
}
fmt.Println("長さ:", builder2.Len())
}
目安:
| シナリオ | 推奨方法 |
|---|---|
| 2〜3個の文字列連結 | +またはfmt.Sprintf |
| ループ内の連結(回数既知) | strings.Builder + Grow() |
| ループ内の連結(回数不明) | strings.Builder |
質問3:文字列が特定の文字のみを含むかチェックするにはどうすればいいですか?
GO
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Hello123"
// 文字と数字のみを含むかチェック
isAlphanumeric := true
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
isAlphanumeric = false
break
}
}
fmt.Println("英数字のみ:", isAlphanumeric)
// ASCII文字のみを含むかチェック
isASCII := true
for _, r := range s {
if r > 127 {
isASCII = false
break
}
}
fmt.Println("ASCIIのみ:", isASCII)
// 特定の文字セットのみを含むかチェック
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
containsOnlyAllowed := true
for _, r := range s {
if !strings.ContainsRune(allowed, r) {
containsOnlyAllowed = false
break
}
}
fmt.Println("許可範囲内:", containsOnlyAllowed)
}
質問4:strings.Containsと正規表現のどちらを什么时候使用すべきですか?
GO
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
text := "My email is test@example.com, phone is 13800138000"
// シンプルな検索 → stringsパッケージを使用(高速)
fmt.Println(strings.Contains(text, "example.com")) // true
// パターンマッチング → 正規表現を使用
emailRegex := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)
fmt.Println("メール:", emailRegex.FindString(text))
phoneRegex := regexp.MustCompile(`1[3-9]\d{9}`)
fmt.Println("電話:", phoneRegex.FindString(text))
}
ガイドライン:
- 固定文字列の検索:
stringsパッケージを使用 — パフォーマンスが良い - パターンマッチング(例:メール、電話番号の形式):
regexpパッケージを使用 - ループ内で正規表現を繰り返しコンパイルするのは避けてください。事前にコンパイルしてください
📖 まとめ
| トピック | 主な内容 |
|---|---|
| stringsパッケージ | Contains, HasPrefix, HasSuffix, Index, Replace, Split, Join, Trim, ToUpper, ToLower, Count, Repeat, Fields |
| strconvパッケージ | Atoi, Itoa, ParseBool, ParseFloat, FormatInt, FormatFloat |
| unicode/utf8 | RuneCountInString, ValidString, unicode.IsLetter/IsDigit/IsSpace |
| strings.Builder | WriteString, WriteRune, WriteByte, Grow, String, Len |
| コア原則 | 文字列は不変、lenはバイト数を返す、rangeはrune単位で繰り返し、多数の連結にはBuilderを使用 |
📝 演習
演習1:基礎 — 文字列の反転
文字列を反転する関数reverseString(s string) stringを書いてください。中国語の文字を正しく処理できる必要があります。
ヒント: 文字列を単純に[]byteに変換して反転することはできません。中国語の文字は複数バイトを占めるためです。
GO
// 期待結果
reverseString("Hello") // "olleH"
reverseString("Go语言") // "言语oG"
演習2:中級 — キャメルケースとスネークケースの変換
2つの関数を書いてください:
camelToSnake(s string) string:キャメルケースをスネークケースに変換snakeToCamel(s string) string:スネークケースをキャメルケースに変換
ヒント: unicode.IsUpperを使用して大文字の位置を検出してください。
GO
// 期待結果
camelToSnake("helloWorld") // "hello_world"
camelToSnake("HTTPResponse") // "http_response"
snakeToCamel("hello_world") // "helloWorld"
snakeToCamel("http_response") // "httpResponse"
演習3:チャレンジ — シンプルなMarkdown見出し抽出器
Markdown文書からすべての見出しを抽出する関数extractHeadings(md string) []stringを書いてください。
ヒント: 見出しは#で始まり、#の数が見出しレベルを示します。
GO
// 入力
md := `# Heading 1
This is body text
## Heading 2
### Heading 3
## Another Heading 2`
// 期待出力
// ["# Heading 1", "## Heading 2", "### Heading 3", "## Another Heading 2"]
次のレッスン
文字列処理を完了しました!次のレッスンでは、ファイルI/O操作について学びます。ファイルの読み書き、ディレクトリの処理、バッファリングされたI/Oによるパフォーマンス向上について解説します。



