Go のスライスの応用パターン(フィルタ・変換・削除)

実際の開発では、スライスをフィルタリングしたり、要素を変換したり、特定の要素を削除したりする場面が頻繁にあります。Go でよく使われるイディオムを紹介します。

フィルタパターン

条件に合う要素だけを抽出する基本パターンです。

numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// 偶数だけを抽出
evens := []int{}
for _, n := range numbers {
    if n%2 == 0 {
        evens = append(evens, n)
    }
}

fmt.Println(evens) // [2 4 6 8 10]

パフォーマンスを意識する場合は、あらかじめ容量を確保しておくと効率的です。

evens := make([]int, 0, len(numbers))
for _, n := range numbers {
    if n%2 == 0 {
        evens = append(evens, n)
    }
}

マップ(変換)パターン

各要素を変換して新しいスライスを作るパターンです。

numbers := []int{1, 2, 3, 4, 5}

// 各要素を2倍にする
doubled := make([]int, len(numbers))
for i, n := range numbers {
    doubled[i] = n * 2
}

fmt.Println(doubled) // [2 4 6 8 10]

変換後の要素数が変わらない場合は、make であらかじめ長さを指定しておくと、append を使わずに済みます。

要素の削除(インデックス指定)

特定のインデックスの要素を削除するには、append とスライシングを組み合わせます。

numbers := []int{0, 1, 2, 3, 4, 5}
i := 2 // 削除するインデックス

numbers = append(numbers[:i], numbers[i+1:]...)
fmt.Println(numbers) // [0 1 3 4 5]

この方法は順序を保持しますが、削除位置より後ろの要素をすべて移動するため、大きなスライスでは遅くなります。

要素の削除(順序を保持しない)

順序が重要でない場合は、末尾の要素と入れ替えてから末尾を削る方法が高速です。

numbers := []int{0, 1, 2, 3, 4, 5}
i := 2 // 削除するインデックス

numbers[i] = numbers[len(numbers)-1]
numbers = numbers[:len(numbers)-1]
fmt.Println(numbers) // [0 1 5 3 4]

要素の移動が 1 回で済むため、大きなスライスでも効率的です。

条件に合う要素を削除

条件に合う要素をすべて削除するには、フィルタパターンの逆を使います。

numbers := []int{1, 2, 3, 4, 5, 6}

// 偶数を削除(奇数だけ残す)
result := numbers[:0] // 同じ背後の配列を再利用
for _, n := range numbers {
    if n%2 != 0 {
        result = append(result, n)
    }
}

fmt.Println(result) // [1 3 5]

numbers[:0] で長さ 0 のスライスを作り、同じ背後の配列を再利用することで、メモリ割り当てを減らせます。

重複の除去

スライスから重複を除去するには、マップを使って既出の要素を追跡します。

numbers := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}

seen := make(map[int]bool)
unique := []int{}

for _, n := range numbers {
    if !seen[n] {
        seen[n] = true
        unique = append(unique, n)
    }
}

fmt.Println(unique) // [1 2 3 4 5]

この方法は元の順序を保持します。順序が不要で、単に重複を除きたいだけならマップのキーとして使う方法もあります。

逆順にする

スライスを逆順にするには、先頭と末尾から要素を入れ替えていきます。

numbers := []int{1, 2, 3, 4, 5}

for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 {
    numbers[i], numbers[j] = numbers[j], numbers[i]
}

fmt.Println(numbers) // [5 4 3 2 1]

要素の挿入

特定の位置に要素を挿入するには、append を組み合わせます。

numbers := []int{0, 1, 2, 4, 5}
i := 3     // 挿入位置
v := 3     // 挿入する値

numbers = append(numbers[:i], append([]int{v}, numbers[i:]...)...)
fmt.Println(numbers) // [0 1 2 3 4 5]

この方法は一時的なスライスを作成するため、パフォーマンスが重要な場合は別の方法を検討してください。

// より効率的な挿入
numbers := []int{0, 1, 2, 4, 5}
i := 3
v := 3

numbers = append(numbers, 0)          // 末尾に空き要素を追加
copy(numbers[i+1:], numbers[i:])      // i以降を1つ後ろにずらす
numbers[i] = v                        // 挿入

fmt.Println(numbers) // [0 1 2 3 4 5]

これらのパターンを覚えておくと、日常的なスライス操作を効率よく書けるようになります。