String Manipulation in Golang

String Manipulation in Golang

Go provides a rich set of functions for working with strings. The strings and strconv packages offer comprehensive string manipulation capabilities. Understanding these functions is essential for text processing, parsing, and formatting in Go applications.

Basic String Operations

String Length and Access

basic-strings.go
package main

import "fmt"

func main() {
    s := "Hello, 世界"
    
    fmt.Printf("String: %s\n", s)
    fmt.Printf("Length: %d\n", len(s))  // Length in bytes
    fmt.Printf("Runes: %d\n", len([]rune(s))) // Length in runes
    
    // Access individual bytes
    for i := 0; i < len(s); i++ {
        fmt.Printf("Byte %d: %x\n", i, s[i])
    }
    
    // Access individual runes
    for i, r := range s {
        fmt.Printf("Rune at %d: %c (%U)\n", i, r, r)
    }
}

String Concatenation

concatenation.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Method 1: Using + operator
    s1 := "Hello"
    s2 := "World"
    result1 := s1 + " " + s2
    fmt.Println("Concatenation with +:", result1)
    
    // Method 2: Using strings.Join
    parts := []string{"Hello", "beautiful", "world"}
    result2 := strings.Join(parts, " ")
    fmt.Println("Join result:", result2)
    
    // Method 3: Using fmt.Sprintf
    result3 := fmt.Sprintf("%s %s", s1, s2)
    fmt.Println("Sprintf result:", result3)
    
    // Method 4: Using strings.Builder (most efficient for multiple concatenations)
    var builder strings.Builder
    builder.WriteString("Hello")
    builder.WriteString(" ")
    builder.WriteString("World")
    builder.WriteString("!")
    result4 := builder.String()
    fmt.Println("Builder result:", result4)
}

The strings Package

The strings package provides many useful functions for string manipulation.

Case Conversion

case-conversion.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World!"
    
    fmt.Println("Original:", s)
    fmt.Println("To Upper:", strings.ToUpper(s))
    fmt.Println("To Lower:", strings.ToLower(s))
    fmt.Println("Title Case:", strings.Title(s))
    
    // Check case
    fmt.Println("Is Upper:", s == strings.ToUpper(s))
    fmt.Println("Is Lower:", s == strings.ToLower(s))
}

Searching and Finding

searching.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "The quick brown fox jumps over the lazy dog"
    
    // Contains
    fmt.Println("Contains 'fox':", strings.Contains(s, "fox"))
    fmt.Println("Contains 'cat':", strings.Contains(s, "cat"))
    
    // Index
    fmt.Println("Index of 'fox':", strings.Index(s, "fox"))
    fmt.Println("Last index of 'o':", strings.LastIndex(s, "o"))
    
    // HasPrefix/HasSuffix
    fmt.Println("Has prefix 'The':", strings.HasPrefix(s, "The"))
    fmt.Println("Has suffix 'dog':", strings.HasSuffix(s, "dog"))
    
    // Count
    fmt.Println("Count 'o':", strings.Count(s, "o"))
}

Splitting and Joining

splitting.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "apple,banana,cherry,date"
    
    // Split by comma
    fruits := strings.Split(s, ",")
    fmt.Println("Split result:", fruits)
    
    // Split only first N
    parts := strings.SplitN(s, ",", 2)
    fmt.Println("SplitN(2):", parts)
    
    // Split after last N
    parts2 := strings.SplitAfterN(s, ",", 3)
    fmt.Println("SplitAfterN(3):", parts2)
    
    // Join back
    joined := strings.Join(fruits, " | ")
    fmt.Println("Joined:", joined)
    
    // Split by whitespace
    sentence := "The quick brown fox"
    words := strings.Fields(sentence)
    fmt.Println("Fields:", words)
}

Trimming and Cleaning

trimming.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "  \t Hello, World! \n  "
    
    fmt.Printf("Original: %q\n", s)
    fmt.Printf("Trim spaces: %q\n", strings.TrimSpace(s))
    fmt.Printf("Trim left: %q\n", strings.TrimLeft(s, " \t"))
    fmt.Printf("Trim right: %q\n", strings.TrimRight(s, " \n"))
    
    // Trim specific characters
    data := "...hello..."
    fmt.Printf("Trim '.': %q\n", strings.Trim(data, "."))
    fmt.Printf("Trim left '.': %q\n", strings.TrimLeft(data, "."))
    fmt.Printf("Trim right '.': %q\n", strings.TrimRight(data, "."))
    
    // Trim function
    messy := "!!!Hello!!!"
    clean := strings.TrimFunc(messy, func(r rune) bool {
        return r == '!'
    })
    fmt.Printf("TrimFunc result: %q\n", clean)
}

Replacing

replacing.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "The quick brown fox jumps over the lazy dog"
    
    // Replace first occurrence
    result1 := strings.Replace(s, "o", "0", 1)
    fmt.Println("Replace first 'o':", result1)
    
    // Replace all occurrences
    result2 := strings.ReplaceAll(s, "o", "0")
    fmt.Println("Replace all 'o':", result2)
    
    // Replace with limit
    result3 := strings.Replace(s, "o", "0", 2)
    fmt.Println("Replace 2 'o's:", result3)
    
    // Map function for character replacement
    result4 := strings.Map(func(r rune) rune {
        switch r {
        case 'a', 'e', 'i', 'o', 'u':
            return '*'
        default:
            return r
        }
    }, s)
    fmt.Println("Vowels replaced:", result4)
}

Repeating and Padding

repeating.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Repeat string
    repeated := strings.Repeat("ha", 3)
    fmt.Println("Repeat 'ha' 3 times:", repeated)
    
    // Custom padding functions
    leftPad := func(s string, length int, pad string) string {
        if len(s) >= length {
            return s
        }
        return strings.Repeat(pad, length-len(s)) + s
    }
    
    rightPad := func(s string, length int, pad string) string {
        if len(s) >= length {
            return s
        }
        return s + strings.Repeat(pad, length-len(s))
    }
    
    fmt.Println("Left pad 'hello' to 10 chars:", leftPad("hello", 10, "*"))
    fmt.Println("Right pad 'hello' to 10 chars:", rightPad("hello", 10, "*"))
}

The strconv Package

The strconv package provides conversions between strings and other data types.

Number Conversions

strconv-numbers.go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // String to int
    if i, err := strconv.Atoi("42"); err == nil {
        fmt.Printf("String to int: %d (type: %T)\n", i, i)
    }
    
    // Int to string
    s := strconv.Itoa(42)
    fmt.Printf("Int to string: %s (type: %T)\n", s, s)
    
    // Parse integers with different bases
    if i, err := strconv.ParseInt("1010", 2, 64); err == nil {
        fmt.Printf("Binary '1010' to int: %d\n", i)
    }
    
    if i, err := strconv.ParseInt("FF", 16, 64); err == nil {
        fmt.Printf("Hex 'FF' to int: %d\n", i)
    }
    
    // Float conversions
    if f, err := strconv.ParseFloat("3.14159", 64); err == nil {
        fmt.Printf("String to float: %.2f\n", f)
    }
    
    s2 := strconv.FormatFloat(3.14159, 'f', 2, 64)
    fmt.Printf("Float to string: %s\n", s2)
}

Boolean Conversions

strconv-bool.go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // String to bool
    if b, err := strconv.ParseBool("true"); err == nil {
        fmt.Printf("String 'true' to bool: %t\n", b)
    }
    
    if b, err := strconv.ParseBool("1"); err == nil {
        fmt.Printf("String '1' to bool: %t\n", b)
    }
    
    // Bool to string
    s1 := strconv.FormatBool(true)
    s2 := strconv.FormatBool(false)
    fmt.Printf("Bool to string: %s, %s\n", s1, s2)
}

Advanced String Processing

Working with UTF-8

utf8-processing.go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Hello, 世界! 🌍"
    
    fmt.Printf("String: %s\n", s)
    fmt.Printf("Bytes: %d\n", len(s))
    fmt.Printf("Runes: %d\n", utf8.RuneCountInString(s))
    
    // Decode runes
    for i, r := range s {
        fmt.Printf("Position %d: %c (%U)\n", i, r, r)
    }
    
    // Manual decoding
    bytes := []byte(s)
    for len(bytes) > 0 {
        r, size := utf8.DecodeRune(bytes)
        fmt.Printf("Rune: %c, Size: %d bytes\n", r, size)
        bytes = bytes[size:]
    }
    
    // Check if valid UTF-8
    fmt.Printf("Valid UTF-8: %t\n", utf8.ValidString(s))
}

Regular Expressions

regex-strings.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := "The email is [email protected] and phone is 123-456-7890"
    
    // Find email pattern
    emailRegex := regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`)
    email := emailRegex.FindString(text)
    fmt.Printf("Found email: %s\n", email)
    
    // Find phone pattern
    phoneRegex := regexp.MustCompile(`\d{3}-\d{3}-\d{4}`)
    phone := phoneRegex.FindString(text)
    fmt.Printf("Found phone: %s\n", phone)
    
    // Replace with regex
    maskedEmail := emailRegex.ReplaceAllString(text, "[EMAIL REDACTED]")
    fmt.Printf("Masked: %s\n", maskedEmail)
    
    // Split by pattern
    words := regexp.MustCompile(`\s+`).Split(text, -1)
    fmt.Printf("Words: %v\n", words)
}

String Building Performance

string-builder.go
package main

import (
    "fmt"
    "strings"
    "time"
)

func concatWithPlus(n int) string {
    s := ""
    for i := 0; i < n; i++ {
        s += fmt.Sprintf("item%d ", i)
    }
    return s
}

func concatWithBuilder(n int) string {
    var builder strings.Builder
    for i := 0; i < n; i++ {
        builder.WriteString(fmt.Sprintf("item%d ", i))
    }
    return builder.String()
}

func benchmarkStringBuilding() {
    n := 10000
    
    // Benchmark +
    start := time.Now()
    result1 := concatWithPlus(n)
    plusTime := time.Since(start)
    
    // Benchmark Builder
    start = time.Now()
    result2 := concatWithBuilder(n)
    builderTime := time.Since(start)
    
    fmt.Printf("Plus operator: %v\n", plusTime)
    fmt.Printf("String builder: %v\n", builderTime)
    fmt.Printf("Builder is %.1fx faster\n", float64(plusTime)/float64(builderTime))
    fmt.Printf("Results equal: %t\n", result1 == result2)
}

func main() {
    benchmarkStringBuilding()
}

Common Patterns

URL Encoding/Decoding

url-encoding.go
package main

import (
    "fmt"
    "net/url"
)

func main() {
    original := "Hello, 世界! This has spaces & special chars?"
    
    // Encode
    encoded := url.QueryEscape(original)
    fmt.Printf("Encoded: %s\n", encoded)
    
    // Decode
    decoded, err := url.QueryUnescape(encoded)
    if err != nil {
        fmt.Println("Decode error:", err)
        return
    }
    fmt.Printf("Decoded: %s\n", decoded)
    fmt.Printf("Round trip successful: %t\n", original == decoded)
}

Path Manipulation

path-manipulation.go
package main

import (
    "fmt"
    "path/filepath"
    "strings"
)

func main() {
    // File path manipulation
    path := "/home/user/documents/file.txt"
    
    fmt.Println("Full path:", path)
    fmt.Println("Directory:", filepath.Dir(path))
    fmt.Println("Filename:", filepath.Base(path))
    fmt.Println("Extension:", filepath.Ext(path))
    
    // Clean path
    messyPath := "/home/user/../user/./documents/file.txt"
    cleanPath := filepath.Clean(messyPath)
    fmt.Println("Cleaned path:", cleanPath)
    
    // Join paths
    joined := filepath.Join("/home", "user", "documents", "file.txt")
    fmt.Println("Joined path:", joined)
    
    // String path manipulation
    urlPath := "/api/v1/users/123/posts"
    parts := strings.Split(strings.Trim(urlPath, "/"), "/")
    fmt.Printf("URL parts: %v\n", parts)
}

Template Processing

template-processing.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    template := "Hello, {name}! Welcome to {place}."
    
    // Simple replacement
    result := strings.ReplaceAll(template, "{name}", "Alice")
    result = strings.ReplaceAll(result, "{place}", "Wonderland")
    fmt.Println("Simple replacement:", result)
    
    // Multiple replacements
    replacements := map[string]string{
        "{name}":  "Bob",
        "{place}": "the office",
        "{time}":  "9 AM",
    }
    
    result2 := template
    for old, new := range replacements {
        result2 = strings.ReplaceAll(result2, old, new)
    }
    fmt.Println("Multiple replacements:", result2)
}

Best Practices

  1. Use strings.Builder for concatenation - Especially in loops
  2. Be aware of character encoding - Go strings are UTF-8 encoded
  3. Use appropriate trimming functions - TrimSpace, Trim, TrimFunc
  4. Validate input before conversion - Use strconv functions safely
  5. Consider performance implications - Some operations are expensive
  6. Use regular expressions judiciously - They can be slow for simple operations
  7. Handle errors from conversions - strconv functions can fail

Common Pitfalls

String Immutability

pitfall-immutable.go
package main

import "fmt"

func main() {
    s := "hello"
    
    // This creates a new string, doesn't modify the original
    s2 := strings.ToUpper(s)
    
    fmt.Println("Original:", s)  // "hello"
    fmt.Println("Upper:", s2)    // "HELLO"
}

Byte vs Rune Confusion

pitfall-bytes-runes.go
package main

import "fmt"

func main() {
    s := "世界"  // 2 Chinese characters
    
    fmt.Printf("String: %s\n", s)
    fmt.Printf("Length in bytes: %d\n", len(s))  // 6 bytes (3 bytes per character)
    fmt.Printf("Length in runes: %d\n", len([]rune(s))) // 2 runes
    
    // Wrong way to iterate
    fmt.Println("Byte iteration (wrong):")
    for i := 0; i < len(s); i++ {
        fmt.Printf("Byte %d: %x\n", i, s[i])
    }
    
    // Right way to iterate
    fmt.Println("Rune iteration (correct):")
    for i, r := range s {
        fmt.Printf("Rune at %d: %c (%U)\n", i, r, r)
    }
}

External Resources:

Related Tutorials:

Last updated on