【Go言語入門】スライスの要素追加・削除の完全ガイド|appendと削除の実践方法を解説!

Go

Go言語で配列より柔軟に使える「スライス」は、開発の中で頻繁に登場します。

中でもよく使うのが「要素の追加」と「削除」ですが、他言語に比べてやや癖のある書き方に戸惑う人も多いです。

この記事では、Goのスライスに対して要素を追加・削除する方法を、初心者でも理解できるように丁寧に解説します。

実際のコード例と一緒に、どんな場面で使うかも紹介するので、すぐに実践で活用できるようになります。

スポンサーリンク

Goのスライスとは?

スライスの基本概念

スライスは可変長の配列のようなものです。

内部的には配列を参照しており、容量や長さを動的に変えられるのが特徴です。

身近な例で考えてみよう:

  • 本棚を想像してください
  • 普通の本棚(配列):最初に決めたサイズから変更できない
  • 伸縮する本棚(スライス):本が増えたら自動で拡張される

配列とスライスの違い

特徴配列スライス
サイズ固定可変
宣言方法[5]int{1,2,3,4,5}[]int{1,2,3,4,5}
要素の追加できないappendで可能
メモリ効率予測しやすい動的に調整

スライスの基本的な作成方法

package main

import "fmt"

func main() {
    // 方法1:リテラルで作成
    fruits := []string{"apple", "banana", "cherry"}
    fmt.Println(fruits) // [apple banana cherry]
    
    // 方法2:make関数で作成
    numbers := make([]int, 3, 5) // 長さ3、容量5
    fmt.Println(numbers) // [0 0 0]
    
    // 方法3:空のスライスを作成
    var empty []string
    fmt.Println(empty) // []
    
    // 方法4:空のスライスを作成(より明示的)
    empty2 := []string{}
    fmt.Println(empty2) // []
}

スライスの基本情報を取得

package main

import "fmt"

func main() {
    fruits := []string{"apple", "banana", "cherry"}
    
    // 長さ(現在の要素数)
    fmt.Println("長さ:", len(fruits)) // 長さ: 3
    
    // 容量(メモリ上で確保されている領域)
    fmt.Println("容量:", cap(fruits)) // 容量: 3
    
    // 空かどうかの判定
    if len(fruits) == 0 {
        fmt.Println("空のスライスです")
    } else {
        fmt.Println("要素があります")
    }
}

スライスの内部構造

package main

import "fmt"

func main() {
    // スライスは内部的に以下の情報を持っている
    slice := []int{1, 2, 3}
    
    fmt.Printf("スライス: %v\n", slice)
    fmt.Printf("長さ: %d\n", len(slice))    // 現在の要素数
    fmt.Printf("容量: %d\n", cap(slice))    // メモリ上の最大容量
    fmt.Printf("アドレス: %p\n", slice)     // メモリアドレス
}

この章のまとめ

スライスはGoで配列操作を行ううえで欠かせない基本構造です。次の章では、要素の追加方法を詳しく見ていきます。

スライスへの要素追加

append関数の基本

Goでは標準のappend関数で要素の追加が可能です。

重要なポイント:

  • append新しいスライスを返す
  • 元のスライスは変更されない
  • 結果を元の変数に代入する必要がある

基本的な要素追加

package main

import "fmt"

func main() {
    // 1つの要素を追加
    nums := []int{1, 2, 3}
    fmt.Println("元のスライス:", nums) // [1 2 3]
    
    nums = append(nums, 4)
    fmt.Println("追加後:", nums) // [1 2 3 4]
    
    // さらに追加
    nums = append(nums, 5)
    fmt.Println("さらに追加:", nums) // [1 2 3 4 5]
}

複数要素の一括追加

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}
    
    // 複数の要素を一度に追加
    nums = append(nums, 4, 5, 6, 7)
    fmt.Println(nums) // [1 2 3 4 5 6 7]
    
    // 文字列の例
    fruits := []string{"apple", "banana"}
    fruits = append(fruits, "cherry", "date", "elderberry")
    fmt.Println(fruits) // [apple banana cherry date elderberry]
}

スライス同士の結合

package main

import "fmt"

func main() {
    // 2つのスライスを結合
    a := []int{1, 2, 3}
    b := []int{4, 5, 6}
    
    // ...(スプレッド演算子)でスライスを展開
    a = append(a, b...)
    fmt.Println("結合後:", a) // [1 2 3 4 5 6]
    
    // 文字列スライスの結合例
    group1 := []string{"太郎", "花子"}
    group2 := []string{"次郎", "美咲"}
    allMembers := append(group1, group2...)
    fmt.Println("全メンバー:", allMembers) // [太郎 花子 次郎 美咲]
}

先頭への要素追加

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}
    
    // 先頭に要素を追加(少し特殊な書き方)
    nums = append([]int{1}, nums...)
    fmt.Println(nums) // [1 2 3 4]
    
    // 先頭に複数要素を追加
    nums = append([]int{-1, 0}, nums...)
    fmt.Println(nums) // [-1 0 1 2 3 4]
}

指定位置への要素挿入

package main

import "fmt"

func main() {
    fruits := []string{"apple", "cherry", "date"}
    
    // 1番目(appleとcherryの間)にbananaを挿入
    index := 1
    newElement := "banana"
    
    // 挿入処理:スライスを分割して間に要素を挟む
    fruits = append(fruits[:index], append([]string{newElement}, fruits[index:]...)...)
    fmt.Println(fruits) // [apple banana cherry date]
}

より読みやすい挿入関数

package main

import "fmt"

// 指定位置に要素を挿入する関数
func insertAt(slice []string, index int, element string) []string {
    // 範囲チェック
    if index < 0 || index > len(slice) {
        return slice // エラーの場合は元のスライスを返す
    }
    
    // 新しいスライスを作成して挿入
    result := make([]string, len(slice)+1)
    copy(result[:index], slice[:index])        // 挿入位置より前をコピー
    result[index] = element                    // 新しい要素を配置
    copy(result[index+1:], slice[index:])      // 挿入位置以降をコピー
    
    return result
}

func main() {
    fruits := []string{"apple", "cherry", "date"}
    fruits = insertAt(fruits, 1, "banana")
    fmt.Println(fruits) // [apple banana cherry date]
}

容量とパフォーマンス

package main

import "fmt"

func main() {
    // 容量の変化を観察
    slice := make([]int, 0, 2) // 初期容量2
    fmt.Printf("初期状態 - 長さ: %d, 容量: %d\n", len(slice), cap(slice))
    
    slice = append(slice, 1)
    fmt.Printf("1個追加後 - 長さ: %d, 容量: %d\n", len(slice), cap(slice))
    
    slice = append(slice, 2)
    fmt.Printf("2個追加後 - 長さ: %d, 容量: %d\n", len(slice), cap(slice))
    
    slice = append(slice, 3) // 容量を超える
    fmt.Printf("3個追加後 - 長さ: %d, 容量: %d\n", len(slice), cap(slice))
    // 容量が自動的に拡張される(通常は2倍になる)
}

実用例:動的なデータ収集

package main

import "fmt"

func main() {
    // ユーザー入力を模擬したデータ収集
    var scores []int
    
    // テストの点数を順次追加
    testScores := []int{85, 92, 78, 96, 88}
    
    for i, score := range testScores {
        scores = append(scores, score)
        fmt.Printf("%d回目のテスト後: %v\n", i+1, scores)
    }
    
    // 平均を計算
    total := 0
    for _, score := range scores {
        total += score
    }
    average := float64(total) / float64(len(scores))
    fmt.Printf("平均点: %.1f\n", average)
}

この章のまとめ

appendを活用すれば、柔軟にスライスの拡張が可能です。次は、少し難しい要素の削除について解説します。

スライスからの要素削除

削除の基本概念

重要なポイント:

  • Goのスライスには直接的な削除関数がない
  • 削除はスライスの再構築で実現
  • 削除方法は主に3つのパターンがある

方法1:インデックスを指定して削除

基本的な削除パターン

package main

import "fmt"

func main() {
    fruits := []string{"apple", "banana", "cherry", "date"}
    fmt.Println("元のスライス:", fruits)
    
    // 1番目(banana)を削除
    index := 1
    fruits = append(fruits[:index], fruits[index+1:]...)
    fmt.Println("削除後:", fruits) // [apple cherry date]
}

削除の仕組みを詳しく説明

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    fmt.Println("元のスライス:", numbers)
    
    // 2番目(30)を削除する場合
    index := 2
    
    // ステップ1:削除位置より前の部分
    before := numbers[:index]  // [10, 20]
    fmt.Println("前の部分:", before)
    
    // ステップ2:削除位置より後の部分
    after := numbers[index+1:] // [40, 50]
    fmt.Println("後の部分:", after)
    
    // ステップ3:前と後を結合
    numbers = append(before, after...)
    fmt.Println("削除後:", numbers) // [10 20 40 50]
}

安全な削除関数

package main

import "fmt"

// 指定インデックスの要素を削除する関数
func removeAt(slice []int, index int) []int {
    // 範囲チェック
    if index < 0 || index >= len(slice) {
        return slice // 範囲外の場合は元のスライスを返す
    }
    
    // 削除実行
    return append(slice[:index], slice[index+1:]...)
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    fmt.Println("元のスライス:", numbers)
    
    // 2番目の要素(3)を削除
    numbers = removeAt(numbers, 2)
    fmt.Println("削除後:", numbers) // [1 2 4 5]
    
    // 存在しないインデックスを指定(エラーにならない)
    numbers = removeAt(numbers, 10)
    fmt.Println("範囲外削除:", numbers) // [1 2 4 5] (変更なし)
}

方法2:値を指定して削除

最初に見つかった要素を削除

package main

import "fmt"

// 指定した値を持つ最初の要素を削除
func removeValue(slice []string, value string) []string {
    for i, v := range slice {
        if v == value {
            return append(slice[:i], slice[i+1:]...)
        }
    }
    return slice // 見つからない場合は元のスライスを返す
}

func main() {
    fruits := []string{"apple", "banana", "cherry", "banana", "date"}
    fmt.Println("元のスライス:", fruits)
    
    // "banana"を削除(最初の1つだけ)
    fruits = removeValue(fruits, "banana")
    fmt.Println("banana削除後:", fruits) // [apple cherry banana date]
}

指定した値をすべて削除

package main

import "fmt"

// 指定した値を持つすべての要素を削除
func removeAllValues(slice []string, value string) []string {
    var result []string
    for _, v := range slice {
        if v != value {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    fruits := []string{"apple", "banana", "cherry", "banana", "date"}
    fmt.Println("元のスライス:", fruits)
    
    // "banana"をすべて削除
    fruits = removeAllValues(fruits, "banana")
    fmt.Println("banana全削除後:", fruits) // [apple cherry date]
}

方法3:条件に合致する要素の削除

基本的な条件削除

package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println("元のスライス:", numbers)
    
    // 偶数を削除(奇数だけ残す)
    var oddNumbers []int
    for _, num := range numbers {
        if num%2 != 0 { // 奇数の場合
            oddNumbers = append(oddNumbers, num)
        }
    }
    
    fmt.Println("奇数のみ:", oddNumbers) // [1 3 5 7 9]
}

より複雑な条件での削除

package main

import "fmt"

type Student struct {
    Name  string
    Score int
}

func main() {
    students := []Student{
        {"太郎", 85},
        {"花子", 92},
        {"次郎", 76},
        {"美咲", 88},
        {"健太", 65},
    }
    
    fmt.Println("全学生:")
    for _, s := range students {
        fmt.Printf("  %s: %d点\n", s.Name, s.Score)
    }
    
    // 80点以上の学生のみを残す
    var highScorers []Student
    for _, student := range students {
        if student.Score >= 80 {
            highScorers = append(highScorers, student)
        }
    }
    
    fmt.Println("\n80点以上の学生:")
    for _, s := range highScorers {
        fmt.Printf("  %s: %d点\n", s.Name, s.Score)
    }
}

効率的な削除方法(in-place削除)

package main

import "fmt"

// メモリ効率の良い削除方法
func removeEvenInPlace(slice []int) []int {
    writeIndex := 0
    
    for _, value := range slice {
        if value%2 != 0 { // 奇数の場合
            slice[writeIndex] = value
            writeIndex++
        }
    }
    
    return slice[:writeIndex] // 有効な部分だけを返す
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println("元のスライス:", numbers)
    
    // 偶数を効率的に削除
    numbers = removeEvenInPlace(numbers)
    fmt.Println("偶数削除後:", numbers) // [1 3 5 7 9]
}

先頭・末尾要素の削除

package main

import "fmt"

func main() {
    fruits := []string{"apple", "banana", "cherry", "date"}
    fmt.Println("元のスライス:", fruits)
    
    // 先頭要素を削除
    if len(fruits) > 0 {
        fruits = fruits[1:]
        fmt.Println("先頭削除後:", fruits) // [banana cherry date]
    }
    
    // 末尾要素を削除
    if len(fruits) > 0 {
        fruits = fruits[:len(fruits)-1]
        fmt.Println("末尾削除後:", fruits) // [banana cherry]
    }
}

削除操作での注意点

package main

import "fmt"

func main() {
    // 注意:削除中にスライスを変更するとバグが起きる
    numbers := []int{1, 2, 3, 4, 5}
    
    // ❌ 悪い例:削除中にインデックスがズレる
    // for i, num := range numbers {
    //     if num%2 == 0 {
    //         numbers = append(numbers[:i], numbers[i+1:]...)
    //     }
    // }
    
    // ✅ 良い例:新しいスライスを作成
    var result []int
    for _, num := range numbers {
        if num%2 != 0 { // 奇数のみ残す
            result = append(result, num)
        }
    }
    
    fmt.Println("正しい削除結果:", result) // [1 3 5]
}

この章のまとめ

削除処理ではスライスの再構築が基本になります。次の章では、追加・削除を実務にどう活かすかを紹介します。

実務でのスライス操作活用例

1. データフィルタリング

ユーザー権限の管理

package main

import "fmt"

type User struct {
    Name string
    Role string
    Active bool
}

func main() {
    users := []User{
        {"admin", "administrator", true},
        {"guest", "guest", false},
        {"user1", "user", true},
        {"user2", "user", false},
        {"moderator", "moderator", true},
    }
    
    // アクティブなユーザーのみ抽出
    var activeUsers []User
    for _, user := range users {
        if user.Active {
            activeUsers = append(activeUsers, user)
        }
    }
    
    fmt.Println("アクティブユーザー:")
    for _, user := range activeUsers {
        fmt.Printf("  %s (%s)\n", user.Name, user.Role)
    }
    
    // 管理者権限ユーザーのみ抽出
    var adminUsers []User
    for _, user := range users {
        if user.Role == "administrator" || user.Role == "moderator" {
            adminUsers = append(adminUsers, user)
        }
    }
    
    fmt.Println("\n管理者権限ユーザー:")
    for _, user := range adminUsers {
        fmt.Printf("  %s (%s)\n", user.Name, user.Role)
    }
}

Webアプリでのデータフィルタリング

package main

import (
    "fmt"
    "strings"
)

type Product struct {
    ID    int
    Name  string
    Price int
    Category string
}

// 価格範囲でフィルタリング
func filterByPriceRange(products []Product, minPrice, maxPrice int) []Product {
    var filtered []Product
    for _, product := range products {
        if product.Price >= minPrice && product.Price <= maxPrice {
            filtered = append(filtered, product)
        }
    }
    return filtered
}

// カテゴリでフィルタリング
func filterByCategory(products []Product, category string) []Product {
    var filtered []Product
    for _, product := range products {
        if product.Category == category {
            filtered = append(filtered, product)
        }
    }
    return filtered
}

// 名前で検索
func searchByName(products []Product, keyword string) []Product {
    var filtered []Product
    for _, product := range products {
        if strings.Contains(strings.ToLower(product.Name), strings.ToLower(keyword)) {
            filtered = append(filtered, product)
        }
    }
    return filtered
}

func main() {
    products := []Product{
        {1, "ノートPC", 80000, "電子機器"},
        {2, "スマートフォン", 50000, "電子機器"},
        {3, "コーヒーメーカー", 15000, "家電"},
        {4, "本棚", 25000, "家具"},
        {5, "ゲーミングチェア", 35000, "家具"},
    }
    
    // 価格帯で絞り込み(2万円〜6万円)
    affordable := filterByPriceRange(products, 20000, 60000)
    fmt.Println("2万円〜6万円の商品:")
    for _, p := range affordable {
        fmt.Printf("  %s: %d円\n", p.Name, p.Price)
    }
    
    // カテゴリで絞り込み
    electronics := filterByCategory(products, "電子機器")
    fmt.Println("\n電子機器:")
    for _, p := range electronics {
        fmt.Printf("  %s: %d円\n", p.Name, p.Price)
    }
    
    // 名前で検索
    searchResults := searchByName(products, "ゲーミング")
    fmt.Println("\n「ゲーミング」を含む商品:")
    for _, p := range searchResults {
        fmt.Printf("  %s: %d円\n", p.Name, p.Price)
    }
}

2. 動的なデータ構築

設定ファイルの動的生成

package main

import "fmt"

type ConfigItem struct {
    Key   string
    Value string
}

func main() {
    var config []ConfigItem
    
    // 基本設定を追加
    config = append(config, ConfigItem{"host", "localhost"})
    config = append(config, ConfigItem{"port", "8080"})
    
    // 環境に応じて設定を追加
    environment := "production" // 実際にはコマンドライン引数や環境変数から取得
    
    if environment == "production" {
        config = append(config, ConfigItem{"ssl", "true"})
        config = append(config, ConfigItem{"log_level", "error"})
    } else {
        config = append(config, ConfigItem{"ssl", "false"})
        config = append(config, ConfigItem{"log_level", "debug"})
    }
    
    // 機能フラグに応じて設定を追加
    features := []string{"auth", "cache", "monitoring"}
    for _, feature := range features {
        config = append(config, ConfigItem{feature + "_enabled", "true"})
    }
    
    // 設定を出力
    fmt.Println("生成された設定:")
    for _, item := range config {
        fmt.Printf("%s = %s\n", item.Key, item.Value)
    }
}

リアルタイムデータの管理

package main

import (
    "fmt"
    "time"
)

type LogEntry struct {
    Timestamp time.Time
    Level     string
    Message   string
}

// ログエントリを追加し、古いエントリを削除
func addLogEntry(logs []LogEntry, entry LogEntry, maxEntries int) []LogEntry {
    logs = append(logs, entry)
    
    // 最大エントリ数を超えた場合、古いエントリを削除
    if len(logs) > maxEntries {
        logs = logs[len(logs)-maxEntries:]
    }
    
    return logs
}

// 特定レベル以上のログのみを抽出
func filterLogsByLevel(logs []LogEntry, minLevel string) []LogEntry {
    levelPriority := map[string]int{
        "debug": 1,
        "info":  2,
        "warn":  3,
        "error": 4,
    }
    
    minPriority := levelPriority[minLevel]
    var filtered []LogEntry
    
    for _, log := range logs {
        if levelPriority[log.Level] >= minPriority {
            filtered = append(filtered, log)
        }
    }
    
    return filtered
}

func main() {
    var logs []LogEntry
    maxLogs := 5
    
    // ログエントリを順次追加
    entries := []LogEntry{
        {time.Now(), "info", "アプリケーション開始"},
        {time.Now(), "debug", "設定ファイル読み込み"},
        {time.Now(), "info", "データベース接続"},
        {time.Now(), "warn", "接続リトライ"},
        {time.Now(), "error", "データベース接続失敗"},
        {time.Now(), "info", "フォールバック処理"},
        {time.Now(), "info", "処理完了"},
    }
    
    for _, entry := range entries {
        logs = addLogEntry(logs, entry, maxLogs)
        fmt.Printf("ログ追加: %s [%s] %s\n", 
            entry.Timestamp.Format("15:04:05"), 
            entry.Level, 
            entry.Message)
    }
    
    fmt.Printf("\n保持されているログ数: %d\n", len(logs))
    
    // エラー以上のログのみ抽出
    criticalLogs := filterLogsByLevel(logs, "error")
    fmt.Printf("\n重要なログ(error以上): %d件\n", len(criticalLogs))
    for _, log := range criticalLogs {
        fmt.Printf("  [%s] %s\n", log.Level, log.Message)
    }
}

3. データ集計と分析

売上データの分析

package main

import "fmt"

type Sale struct {
    Product string
    Amount  int
    Month   int
}

func main() {
    sales := []Sale{
        {"商品A", 100000, 1},
        {"商品B", 150000, 1},
        {"商品A", 120000, 2},
        {"商品C", 80000, 2},
        {"商品A", 110000, 3},
        {"商品B", 180000, 3},
    }
    
    // 商品別売上集計
    productSales := make(map[string]int)
    for _, sale := range sales {
        productSales[sale.Product] += sale.Amount
    }
    
    fmt.Println("商品別売上:")
    for product, total := range productSales {
        fmt.Printf("  %s: %d円\n", product, total)
    }
    
    // 月別売上集計
    monthlySales := make(map[int]int)
    for _, sale := range sales {
        monthlySales[sale.Month] += sale.Amount
    }
    
    fmt.Println("\n月別売上:")
    for month, total := range monthlySales {
        fmt.Printf("  %d月: %d円\n", month, total)
    }
    
    // 特定条件の売上を抽出(10万円以上)
    var highValueSales []Sale
    for _, sale := range sales {
        if sale.Amount >= 100000 {
            highValueSales = append(highValueSales, sale)
        }
    }
    
    fmt.Println("\n高額売上(10万円以上):")
    for _, sale := range highValueSales {
        fmt.Printf("  %s: %d円 (%d月)\n", sale.Product, sale.Amount, sale.Month)
    }
}

4. バッチ処理とキュー管理

タスクキューの実装

package main

import (
    "fmt"
    "time"
)

type Task struct {
    ID          int
    Description string
    Priority    int
    CreatedAt   time.Time
}

// タスクを優先度順に挿入
func addTaskByPriority(tasks []Task, newTask Task) []Task {
    // 適切な位置を見つけて挿入
    for i, task := range tasks {
        if newTask.Priority > task.Priority {
            // この位置に挿入
            return append(tasks[:i], append([]Task{newTask}, tasks[i:]...)...)
        }
    }
    // 最後に追加
    return append(tasks, newTask)
}

// 完了したタスクを削除
func removeCompletedTask(tasks []Task, taskID int) []Task {
    for i, task := range tasks {
        if task.ID == taskID {
            return append(tasks[:i], tasks[i+1:]...)
        }
    }
    return tasks
}

// 期限切れタスクを削除
func removeExpiredTasks(tasks []Task, maxAge time.Duration) []Task {
    now := time.Now()
    var validTasks []Task
    
    for _, task := range tasks {
        if now.Sub(task.CreatedAt) <= maxAge {
            validTasks = append(validTasks, task)
        }
    }
    
    return validTasks
}

func main() {
    var taskQueue []Task
    
    // タスクを追加(優先度付き)
    tasks := []Task{
        {1, "データベースバックアップ", 5, time.Now()},
        {2, "メール送信", 2, time.Now()},
        {3, "レポート生成", 3, time.Now()},
        {4, "緊急メンテナンス", 10, time.Now()},
        {5, "ログ解析", 1, time.Now()},
    }
    
    for _, task := range tasks {
        taskQueue = addTaskByPriority(taskQueue, task)
    }
    
    fmt.Println("優先度順タスクキュー:")
    for i, task := range taskQueue {
        fmt.Printf("%d. [優先度:%d] %s\n", i+1, task.Priority, task.Description)
    }
    
    // 高優先度タスクを処理(削除)
    fmt.Println("\n最高優先度タスクを処理...")
    if len(taskQueue) > 0 {
        processed := taskQueue[0]
        taskQueue = taskQueue[1:]
        fmt.Printf("処理完了: %s\n", processed.Description)
    }
    
    // 特定タスクを完了(削除)
    taskQueue = removeCompletedTask(taskQueue, 2)
    fmt.Println("タスクID 2 を完了")
    
    fmt.Println("\n残りのタスク:")
    for i, task := range taskQueue {
        fmt.Printf("%d. [優先度:%d] %s\n", i+1, task.Priority, task.Description)
    }
}

5. ユニークデータの管理

重複削除とデータクリーニング

package main

import "fmt"

// 重複を削除(順序を保持)
func removeDuplicates(slice []string) []string {
    seen := make(map[string]bool)
    var result []string
    
    for _, item := range slice {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    
    return result
}

// 2つのスライスの共通要素を取得
func intersection(slice1, slice2 []string) []string {
    set := make(map[string]bool)
    var result []string
    
    // 1つ目のスライスの要素をセットに登録
    for _, item := range slice1 {
        set[item] = true
    }
    
    // 2つ目のスライスで共通要素を探す
    seen := make(map[string]bool)
    for _, item := range slice2 {
        if set[item] && !seen[item] {
            result = append(result, item)
            seen[item] = true
        }
    }
    
    return result
}

// 2つのスライスの差分を取得
func difference(slice1, slice2 []string) []string {
    set := make(map[string]bool)
    var result []string
    
    // 2つ目のスライスの要素をセットに登録
    for _, item := range slice2 {
        set[item] = true
    }
    
    // 1つ目のスライスで2つ目にない要素を探す
    for _, item := range slice1 {
        if !set[item] {
            result = append(result, item)
        }
    }
    
    return result
}

func main() {
    // データクリーニングの例
    rawData := []string{
        "apple", "banana", "apple", "cherry", "banana", "date", "cherry", "elderberry"
    }
    
    fmt.Println("元のデータ:", rawData)
    
    // 重複削除
    uniqueData := removeDuplicates(rawData)
    fmt.Println("重複削除後:", uniqueData)
    
    // 集合演算の例
    group1 := []string{"apple", "banana", "cherry"}
    group2 := []string{"banana", "cherry", "date", "elderberry"}
    
    fmt.Println("\nグループ1:", group1)
    fmt.Println("グループ2:", group2)
    
    // 共通要素
    common := intersection(group1, group2)
    fmt.Println("共通要素:", common)
    
    // グループ1のみの要素
    onlyGroup1 := difference(group1, group2)
    fmt.Println("グループ1のみ:", onlyGroup1)
    
    // グループ2のみの要素
    onlyGroup2 := difference(group2, group1)
    fmt.Println("グループ2のみ:", onlyGroup2)
}

6. パフォーマンス最適化

効率的なスライス操作

package main

import (
    "fmt"
    "time"
)

// 非効率な方法:毎回新しいスライスを作成
func inefficientAppend(n int) []int {
    var result []int
    for i := 0; i < n; i++ {
        result = append(result, i)
    }
    return result
}

// 効率的な方法:事前に容量を確保
func efficientAppend(n int) []int {
    result := make([]int, 0, n) // 容量を事前に確保
    for i := 0; i < n; i++ {
        result = append(result, i)
    }
    return result
}

// 最も効率的な方法:インデックスで直接設定
func mostEfficientAppend(n int) []int {
    result := make([]int, n) // 長さを指定して作成
    for i := 0; i < n; i++ {
        result[i] = i
    }
    return result
}

func benchmarkMethod(name string, fn func(int) []int, n int) {
    start := time.Now()
    result := fn(n)
    elapsed := time.Since(start)
    
    fmt.Printf("%s: %v (長さ: %d)\n", name, elapsed, len(result))
}

func main() {
    n := 1000000 // 100万要素
    
    fmt.Printf("%d要素の追加パフォーマンス比較:\n", n)
    
    benchmarkMethod("非効率な方法", inefficientAppend, n)
    benchmarkMethod("効率的な方法", efficientAppend, n)
    benchmarkMethod("最効率な方法", mostEfficientAppend, n)
    
    // メモリ使用量の比較
    fmt.Println("\nメモリ効率の例:")
    
    // 容量を指定しない場合
    slice1 := []int{}
    for i := 0; i < 10; i++ {
        slice1 = append(slice1, i)
        fmt.Printf("要素数: %d, 容量: %d\n", len(slice1), cap(slice1))
    }
    
    fmt.Println("\n容量を事前指定した場合:")
    
    // 容量を事前に指定した場合
    slice2 := make([]int, 0, 10)
    for i := 0; i < 10; i++ {
        slice2 = append(slice2, i)
        fmt.Printf("要素数: %d, 容量: %d\n", len(slice2), cap(slice2))
    }
}

この章のまとめ

スライスの追加・削除テクニックを覚えれば、柔軟で効率的なデータ操作が可能になります。最後に全体をまとめます。

まとめ

重要ポイント

Goのスライスは柔軟で強力なデータ構造です。以下の要点を押さえておきましょう:

  • append関数による追加:新しいスライスを返すため、結果を代入する
  • 削除はスライスの再構築:直接的な削除関数はないため、スライスを分割・結合
  • エラー対策も忘れずに:範囲チェックやnil チェックを行う
  • パフォーマンスを意識:事前の容量確保で効率化

スライス操作のベストプラクティス

1. 適切な初期化

目的推奨方法理由
空のスライス作成[]T{} または make([]T, 0)nilスライスよりも安全
容量が分かっている場合make([]T, 0, capacity)メモリ効率が良い
固定値で初期化[]T{value1, value2}最も直接的

2. エラーハンドリング

// ❌ 悪い例
func badRemove(slice []int, index int) []int {
    return append(slice[:index], slice[index+1:]...)
}

// ✅ 良い例
func goodRemove(slice []int, index int) []int {
    if index < 0 || index >= len(slice) {
        return slice
    }
    return append(slice[:index], slice[index+1:]...)
}

3. 可読性を重視

// ❌ 悪い例(何をしているかわからない)
result := append(data[:i], data[i+1:]...)

// ✅ 良い例(段階的で理解しやすい)
before := data[:i]
after := data[i+1:]
result := append(before, after...)

覚えておきたいスライス操作パターン

よく使う操作

  1. 要素の追加slice = append(slice, element)
  2. 要素の削除slice = append(slice[:i], slice[i+1:]...)
  3. 先頭に追加slice = append([]T{element}, slice...)
  4. スライス結合slice1 = append(slice1, slice2...)

注意が必要な操作

  1. 削除中の変更:イテレーション中の削除は避ける
  2. 容量の管理:大量データでは事前に容量を確保
  3. nilスライス:操作前にnilチェックを行う
  4. 型の一致:appendする要素の型が一致していることを確認

コメント

タイトルとURLをコピーしました