【Go】【チートシート】Slice Tricksの重要なとこだけ 〜忙しい人へ〜

# はじめに

前回は、スライスマスターになりたいので、Slice Tricks（チートシート）を作って見ました💪

しかし、図が大きかったり、長く読みにくい記事を書いてしまったので、

前回記事にした記事の重要なところだけ（記事を書いていて学びの多かったところ）を抜粋しました！🙆

特に、忙しい人向けに、自分が重要だと感じるところを共有する記事（チートシート）です！🙇

元ネタ

https://github.com/golang/go/wiki/SliceTricks

# 🏆 スライスの要素追加, 結合

1.  `for _, i := range b {
    a = append(a, i)
}`

2. `a = append(a, b...)`

```go
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
for _, i := range s2 {
    s1 = append(s1, i)
}

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

s3 := []int{1, 2, 3}
s4 := []int{4, 5, 6}
s3 = append(s3, s4...)

fmt.Println(s3) // [1 2 3 4 5 6]
```
https://go.dev/play/p/sG0I0Fq1oDs


## 💡 **cap（容量）を超えてしまったら?**

**スライス b の全要素**を**スライス a の末尾**に追加するとき、aのcap（容量）を超えてしまったらどうなるか

→ 元のスライスの**2倍のcap（容量）を持つ新しいスライスが作成**され、そこにaのコピーと、末尾にｂが追加される

![スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2885098/2083b9e7-aa06-1847-74ae-c9162ee9ef92.png)


このため、元のスライスの要素数が非常に大きい場合や、追加する要素数が多い場合は、必要となるメモリ量とデータのコピーにかかる時間が増加し、パフォーマンスに影響が出ます💦

:::note info
**対策** ： 事前に必要なスライスのサイズが分かっている場合、または大量のデータを扱う場合は、make 関数を使って十分なcap(容量)を持つスライスを事前に確保しておくことが大切です！
:::


## 💡 **コピーするか直接読み出すか**

大きいなスライスを扱う場合は、`for i　:= range slice`を使う！

`for i, v := range map[] or slice`を使った場合
`i`, `v`にはコピーの操作が走るため、大きなデータになるほど遅くなってしまう💦

```go
s := []int{1, 2, 3, 4, 5}

// インデックス（i）とその値のコピー（v）を生成
for i, v := range s {
    fmt.Println(i) 
    fmt.Println(v)
}

// スライスから直接値を読み出す
for i := range s {
    fmt.Println(s[i]) 
}
```

:::note info
**対策** ： 大きいスライスを扱う場合は、`for i　:= range slice`を使う！　`i`を使って、`slice[i]`で要素にアクセス
:::

# 🏆 スライスのコピー

1. `b = make([]T, len(a))`
`copy(b, a)`
    - copy()関数を用いてaの全要素をbにコピー

2. `b = append([]T(nil), a...)`
    - aの要素は新しいスライスにコピーされ、そのコピーされたスライスがbに代入される

3. `b = append(a[:0:0], a...)`
    - a[:0:0]は、スライスaから新たなスライス（aと同じcap（容量）を持つがlen（長さ）が0の新たなスライス）を作成
    - そして、そのスライスにaの全要素を追加

```go
s1 := []int{1, 2, 3, 4, 5}
b1 := make([]int, len(s1))
n := copy(b1, s1)

fmt.Println(n, b1) // 5 [1 2 3 4 5]

s2 := []int{1, 2, 3, 4, 5}
b2 := append([]int(nil), s2...)

fmt.Println(b2) // 出力: [1 2 3 4 5]

s3 := []int{1, 2, 3, 4, 5}
b3 := append(s3[:0:0], s3...)

fmt.Println(b3) // 出力: [1 2 3 4 5]
```
https://go.dev/play/p/BCNhCV3op12


## 💡 `append`と`copy`について

**`append`関数** 

- スライスの末尾に新しい要素を追加する。
- スライスの容量が十分であれば、新しいメモリ領域を確保することなく操作が行われる。

cap（容量）が足りなくなると（追加する要素がスライスの容量を超える場合）

- **新たなメモリ領域を確保し、元のスライスを確保したメモリ領域にコピー**をします。
- この新たなメモリ領域の確保は、既存のスライス容量の**2倍**の大きさを確保します。

**`copy`関数**

- **新たなメモリ領域を確保をせずに、元のスライスを新たなスライスにコピー**します。

→つまり、cap（容量）が足りなくなる要素のコピーでは、`append`を使用すると、新たなメモリ領域を確保（**メモリアロケーション**）するので、`copy`関数を用いてスライスの操作を行う方がいいと感じます！

:::note info
**結論** : `append`と`copy`は、パフォーマンス検証しながら、適切な方法を選択することが重要ですね🙆‍♂️

- **大きなスライスを扱う時（cap（容量）を超えた操作）**
`copy`関数でスライスの操作を行う方がいいかも
**理由**：`append`だと不必要なメモリの確保と解放が多発（ガベージコレクション（GC）が頻繁に発生）し、パフォーマンスが低下する。


- **小さいスライスを扱う時（cap（容量）内の操作）**
`append`関数でスライスの操作を行う方がいいかも
**理由**：コードの読みやすさやメンテナンス性が上がるかも（append関数のほうが他コードや記事でよく見る👀）
:::

### 参考

`append`と`copy`について詳しく知りたい方へ

https://zenn.dev/mattn/articles/31dfed3c89956d

# 🏆 スライスをソートする
1. `sort.Slice(a, func(i, j T) bool { return a[i] < a[j] })`

    - **不安定ソート**： 並び替えを行う際に、同じ値を持つ要素があっても、元の順序を気にせず相対的な順序になる

2. `sort.SliceStable(a, func(i, j T) bool { return a[i] < a[j] })`

    - **安定ソート**： 並び替えを行う際に、同じ値を持つ要素があったら、元の順序を保持する

3. `sort.Ints(a)`

    - **不安定ソート**： sort.IntSlice(a) を作成し、その上で sort.Sort を実行している。

4. `sort.Stable(sort.IntSlice(a))`

    - **安定ソート**： sort.IntSlice(a) を作成し、その上で sort.Stable を実行している。

```go
// スライスを不安定ソート
s1 := []int{3, 4, 5, 1, 2}

sort.Slice(s1, func(i, j int) bool {
    return s1[i] < s1[j]
})

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

// スライスを安定ソートする
s2 := []int{3, 4, 5, 1, 2}
sort.SliceStable(s2, func(i, j int) bool {
    return s2[i] < s2[j]
})

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

// スライスを不安定ソート
s3 := []int{3, 4, 5, 1, 2}

sort.Ints(s3)

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

// スライスを安定ソートする
s4 := []int{3, 4, 5, 1, 2}

sort.Stable(sort.IntSlice(s4))

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

https://go.dev/play/p/hwr_cy3sWuE

## 💡 安定ソートと不安定ソートの違い

スライスを使って安定ソートと不安定ソートの違いを視覚的に理解するのは難しいので、マップを使って説明します。。。🙏
```go
// スライスを不安定ソート
p := []P{{"A", 20}, {"B", 20}, {"C", 25}, {"D", 20}}
sort.Slice(p, func(i, j int) bool { return p[i].a < p[j].a })
for _, v := range p {
    fmt.Printf("%s: %d\n", v.n, v.a)
}
// "A", "B", "D"(abcd順)の順序はソートごとに変わる可能性がある
// A: 20
// D: 20
// B: 20
// C: 25

// スライスを安定ソートする
p = []P{{"A", 20}, {"B", 20}, {"C", 25}, {"D", 20}}
sort.SliceStable(p, func(i, j int) bool { return p[i].a < p[j].a })
for _, v := range p {
    fmt.Printf("%s: %d\n", v.n, v.a)
}
// "A", "B", "D"(abcd順)の順序はソート前と同じ
// A: 20
// B: 20
// D: 20
// C: 25
```
https://go.dev/play/p/N-2rMSztSXR

# 🏆 メモリリークを防ぐ

**メモリリーク**

プログラムが動作する上で確保したメモリを適切に解放せず、**不要になったメモリ領域がガベージコレクション（不要になったメモリの自動回収）の対象とならない**状況です。これにより、プログラムは実際には必要としていないにもかかわらず、次第に増え続けるメモリ領域を占有し続けることになる（大変だ💦）

この結果、システム全体のパフォーマンスが低下したり、最悪の場合、メモリが枯渇してプログラムがクラッシュしたりする可能性がある。

こんな場面の時（メモリリークの例）

```go
// 大きなスライスを作成
largeSlice := make([]int, 1<<20)  // 1MBのスライス

// スライスからサブスライスを作成
subslice := largeSlice[10:20]

// これ以降、subsliceだけを使って処理を行いたい...
```
**何がダメなのか、、、**

subsliceはlargeSliceの一部を参照しています。
しかし、その後でlargeSliceが不要になったとしても、subsliceがまだlargeSliceを参照しているため、largeSliceはガベージコレクションの対象にならず、そのメモリは解放されない！！！

:::note info
**対策**: 不必要になったデータへの参照を持ち続けないようにする。つまり、新たなスライスにデータを`copy`する
:::

```go
// 大きなスライスを作成
largeSlice := make([]int, 1<<20)  // 1MBのスライス

// 新たなスライスにデータをコピー
subslice := make([]int, 10)
copy(subslice, largeSlice[10:20])

// これ以降、subsliceだけを使って処理を行いたい...
```
subsliceは新たに確保されたメモリ領域にlargeSliceからデータをコピーして作成されています。
そのため、largeSliceが不要になった時点でそのメモリは解放され、メモリリークは発生しない。

## 💡 **sync.Pool**

`sync.Pool`は、頻繁に大きなスライスを作成し、短期間でそのスライスが不要になる場合、オブジェクトの一時的な再利用を可能する！

スライスをプール化することで、メモリアロケーションのコストを削減したいときや、ガベージコレクションの負荷を減らすことができてすごい!😏

```go
// Poolを作成します。ここで、新しいオブジェクトを生成するための関数を定義
// この例では、1024の長さを持つint型のスライスを生成
var pool = &sync.Pool{
	New: func() interface{} {
		return make([]int, 1024)
	},
}

func main() {
	for i := 0; i < 10; i++ {

		//　Get() Poolからスライスを取得
		s := pool.Get().([]int)

		// 何らかのデータ処理
		fmt.Println(i)
		for j := 0; j < 1024; j++ {
			s[j] = i
		}

		//　Put() 使用後はスライスを再利用するためにPoolに戻す
		pool.Put(s)
	}
}
```

https://go.dev/play/p/oERZ6bS0rOE

### 参考

ガベージコレクション（GC）が知らない方へ（見慣れない言語ですが、すごくわかりやすい）

https://qiita.com/tonkotsuboy_com/items/5bc9ce8bde902022d139

`sync.Pool`について

https://logmi.jp/tech/articles/328524

# おわりに

フルの記事はこちらにあります！

https://qiita.com/nakampany/items/d994e67c125fc1014b14