Go1.21でリリースされたslices・mapsパッケージと今までの実装方法を比較してありがたみを知ろう

# はじめに
2023/08/08にGo 1.21が正式にリリースされましたね🎉
個人的にはslicesパッケージとmapsパッケージのリリースがとても嬉しいです。ついにGoでも標準パッケージでsliceやmapの特定要素を削除したり存在チェックができるようになりましたね。

そこで今回は、これらのパッケージで使用頻度が多そうな関数と従来のslice・map操作を比較して、ありがたみを噛み締めていこうと思います。

# slices
### Contains
スライスに特定の要素が存在するかを判定します。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Contains(s, "orange")) // true
    fmt.Println(slices.Contains(s, "banana")) // false
}
```

- 従来の実装例

(直感的なコードですが、パフォーマンス面は度外視しているのでスライスのサイズが大きいと線形的に処理時間が増えます)
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(containsElement(s, "orange")) // true
	fmt.Println(containsElement(s, "banana")) // false
}

func containsElement(slice []string, element string) bool {
	for _, v := range slice {
		if v == element {
			return true
		}
	}
	return false
}
```

### Insert
スライスの任意の位置に要素を追加します。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Insert(s, 2, "banana")) // [apple orange banana grape]
	
	// len()を使用すればappendと同様、末尾に要素を追加します
	fmt.Println(slices.Insert(s, len(s), "lemon")) // [apple orange grape lemon]
}
```

- 従来の実装例
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	s = insert(s, "banana", 2)
	fmt.Println(s) // [apple orange banana grape]
}

func insert(slice []string, element string, i int) []string {
	slice = append(slice[:i+1], slice[i:]...)
	slice[i] = element
	return slice
}
```
### Delete
スライス内の指定した範囲の要素を削除します。
```go
func main() {
	s := []string{"apple", "orange", "grape", "banana"}
	fmt.Println(slices.Delete(s, 1, 3)) // [apple banana]
}
```

- 従来の実装例
```go
func main() {
	s := []string{"apple", "orange", "grape", "banana"}
	s = delete(s, 1, 2)
	fmt.Println(s) // [apple banana]
}

func delete(slice []string, start, end int) []string {
	slice = append(slice[:start], slice[end+1:]...)
	return slice
}
```

### Index
指定した要素のインデックスを返します。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Index(s, "orange")) // 1
}
```

Deleteと組み合わせると、スライスから任意の要素を削除できます。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	if i := slices.Index(s, "orange"); i != -1 {
		fmt.Println(slices.Delete(s, i, i+1)) // [apple grape]
	}
}
```

- 従来の実装例
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(index(s, "orange")) // 1
}

func index(slice []string, element string) int {
	for i, v := range slice {
		if v == element {
			return i
		}
	}
	return -1
}
```
任意の要素の値で削除する実装例は以下の通りです。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(removeElement(s, "orange")) // [apple grape]
}

func removeElement(slice []string, element string) []string {
	for i := 0; i < len(slice); i++ {
		if slice[i] == element {
			slice = append(slice[:i], slice[i+1:]...)
			break
		}
	}
	return slice
}
```

### Equal
スライス同士の全ての要素が同じか比較します。
「等値」ではなく「等価」としての判定なので、アドレスは異なっても各要素の値が同じならtrueになります。
```go
func main() {
	s1 := []string{"apple", "orange", "grape"}
	s2 := []string{"apple", "orange", "grape"}
	s3 := []string{"banana", "lemon"}
	fmt.Println(slices.Equal(s1, s2)) // true
	fmt.Println(slices.Equal(s1, s3)) // false
}
```

- 従来の実装例

こちらはreflect.DeepEqualでもともと比較可能だったのであまりありがたみはないですね。
```go
func main() {
	s1 := []string{"apple", "orange", "grape"}
	s2 := []string{"apple", "orange", "grape"}
	s3 := []string{"banana", "lemon"}
	fmt.Println(reflect.DeepEqual(s1, s2)) // true
	fmt.Println(reflect.DeepEqual(s1, s3)) // false
}
```
### Max
スライス内で最大の要素を返します。stringの場合は辞書順で一番最後になる要素を返します。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Max(s)) // orange
}
```

- 従来の実装例


```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(max(s)) // orange
}

func max(slice []string) string {
	if len(slice) == 0 {
		return ""
	}

	lastString := slice[0]

	for _, str := range slice {
		if str > lastString {
			lastString = str
		}
	}

	return lastString
}
```
### Min
スライス内で最小の要素を返します。stringの場合は辞書順で一番最初になる要素を返します。

```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Min(s)) // apple
}
```

- 従来の実装例
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(min(s)) // apple
}

func min(slice []string) string {
	if len(slice) == 0 {
		return ""
	}

	firstString := slice[0]

	for _, str := range slice {
		if str < firstString {
			firstString = str
		}
	}

	return firstString
}
```

### Sort
スライス内の要素を昇順にします。stringの場合は辞書順にソートします。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	slices.Sort(s)
	fmt.Println(s) // [apple grape orange]
}
```

- 従来の実装例

sortパッケージで実装できます。
[基本的にはクイックソートで、要素数によってアルゴリズムを変えている](https://zenn.dev/spiegel/articles/20200926-standard-sort-package-by-golang)みたいなのでパフォーマンスも悪くなさそうです。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	sort.Sort(sort.StringSlice(s))
	fmt.Println(s) // [apple grape orange]
}
```
### Reverse
スライス内の要素を降順にします。stringの場合は辞書順の逆からソートします。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	slices.Reverse(s)
	fmt.Println(s) // [grape orange apple]
}
```

- 従来の実装

こちらもsort.Sortと同様に既存パッケージで実装できます。
```go
func main() {
	s := []string{"apple", "orange", "grape"}
	sort.Sort(sort.Reverse(sort.StringSlice(s)))
	fmt.Println(s) // [orange grape apple]
}
```

# Maps
Mapsはインデックスがなく順不同なので関数はそれほど多くないです。
Slicesと比べると恩恵はそんなに大きくないかもしれないですね

### Equal
map同士の全ての要素が同じか比較します。
こちらもスライスと同様、「等値」ではなく「等価」としての判定となります。
```go
func main() {
	s1 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s2 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s3 := map[string]string{
		"fruit":   "orange",
		"vehicle": "car",
		"country": "Japan",
	}

	fmt.Println(maps.Equal(s1, s2)) // true
	fmt.Println(maps.Equal(s1, s3)) // false
}
```

- 従来の実装例
```go
func main() {
	s1 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s2 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s3 := map[string]string{
		"fruit":   "orange",
		"vehicle": "car",
		"country": "Japan",
	}

	fmt.Println(equal(s1, s2)) // true
	fmt.Println(equal(s1, s3)) // false
}

func equal(map1, map2 map[string]string) bool {
	if len(map1) != len(map2) {
		return false
	}

	return reflect.DeepEqual(map1, map2)
}
```

### Copy
mapを別のmapにコピーします。
```go
func main() {
	s1 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s2 := map[string]string{}
	maps.Copy(s2, s1)
	fmt.Println(s2) // map[country:Japan fruit:apple vehicle:car]
}
```

- 従来の実装例
```go
func main() {
	s1 := map[string]string{
		"fruit":   "apple",
		"vehicle": "car",
		"country": "Japan",
	}
	s2 := copy(s1)
	fmt.Println(s2) // map[country:Japan fruit:apple vehicle:car]
}

func copy(source map[string]string) map[string]string {
	copiedMap := make(map[string]string, len(source))

	for key, value := range source {
		copiedMap[key] = value
	}

	return copiedMap
}
```

# おわりに
今回はslice、mapのvalueとしてstring型を選択しましたが、従来だと汎用的な関数として使用するために型アサーションやジェネリクスを使用する必要があり、実装量はさらに増えることになります。

なかには既存のパッケージでシンプルに実装できるものもありましたが、これからはslice、map操作を自前で用意することが少なくなり、よりビジネスロジックの実装に集中できるようになりますね。

ではまた。