ありそうでないけど、あったらちょっと嬉しいGoのslice関数を作ってみた

## はじめに
Goを書いていて「sliceってもう少し組み込み関数あったら良いのに。。」と思うことありません？？
僕はPHPを昔書いていたせいか、結構思います笑（PHPってarrayの組み込み関数豊富なんですよね。。）
もちろん[slices](https://pkg.go.dev/golang.org/x/exp/slices)パッケージもあるのですが、ちょっと足りない。。足りないないならば作ってしまおう！
ということで、タイトルの通りGoのslice関数を作ってみました。
みなさんに「確かにちょっと嬉しい！」と思ってもらえたら、僕はめっちゃ嬉しいです！！

**目次**
- [sliceの各要素の数を数える](#sliceの各要素の数を数える)
- [2つのsliceの差分を算出する](#2つのsliceの差分を算出する)
- [複数のsliceをmergeする](#複数のsliceをmergeする)
- [sliceの要素をフィルタリングする](#sliceの要素をフィルタリングする)
- [sliceの要素を削除する](#sliceの要素を削除する)

**環境**
Go 1.19

## sliceの各要素の数を数える
引数のsliceの各要素の数を数えます。
```go
func CountValueSlice[T comparable](slices []T) map[T]int {
	resultMap := map[T]int{}
	for _, v := range slices {
		resultMap[v] += 1
	}
	return resultMap
}
```
例えばこんな感じで重複チェックとかにも使えそうと思ったり。。

```go:main.go
	s := []string{"test1", "test2", "duplicate", "duplicate"}
	m := CountValueSlice(s)
	for k, v := range m {
		if v > 1 {
			fmt.Printf("%vは重複しています。", k) // duplicateは重複しています。
		}
	}
```

## 2つのsliceの差分を算出する
第1引数のsliceから第2引数のsliceを引き算するイメージです。
具体的には第1引数のsliceと第2引数のsliceに重複する要素を、第1引数のsliceから取り除きます。
```go
func DiffSlice[T comparable](slice1, slice2 []T) []T {
	diffSlice := []T{}
	cmpMap := map[T]int{}

	// slice2が各要素が何個あるのかmapに格納
	for _, v := range slice2 {
		cmpMap[v] += 1
	}

	// slice2にある要素をslice1にあるか確認して、なければdiffSliceに格納
	for _, v := range slice1 {
		t, ok := cmpMap[v]
        // slice1の要素がslice2になければ、配列に格納
		if !ok {
		    diffSlice = append(diffSlice, v)
            continue
        }
        if t == 1 {
            delete(cmpMap, v)
        } else {
            cmpMap[v] -= 1
        }
	}
	return diffSlice
}
```

実際に処理するとこんな感じ。
```go:main.go
func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := []int{1, 3, 5, 6}
	d := slices.DiffSlice(slice1, slice2)
	fmt.Println(d) // [2, 4]
}
```

## 複数のsliceをmergeする
2つのsliceをマージするだけなら、append関数で行けるのですが複数のsliceの場合はできないので作ってみました！
ただ、複数のsliceをマージしたい時があるかは別ですが笑
```go
func MergeSlice[T any](slices ...[]T) []T {
	resultSlice := []T{}
	for _, v := range slices {
		resultSlice = append(resultSlice, v...)
	}
	return resultSlice
}
```

実際に動かすとこんな感じ。
まあこれはそんなに使う場面少ないかもですね笑
```go:main.go
func main() {
	slice1 := []string{"a", "b"}
	slice2 := []string{"c", "d"}
	slice3 := []string{"e", "f"}
	slice4 := []string{"g", "h"}
	ms := slices.MergeSlice(slice1, slice2, slice3, slice4)
	fmt.Println(ms) // [a, b, c, d, e, f, g, h]
}
```

## sliceの要素をフィルタリングする
第2引数の関数によって、フィルタリングします。
```go
func FilterSlice[T any](slice []T, fn func(t T) bool) []T {
	resultSlice := []T{}
	for _, v := range slice {
		if fn(v) {
			resultSlice = append(resultSlice, v)
		}
	}
	return resultSlice
}
```

例えば、公開/非公開のステータスをもつDocumentがあって、今回は公開Documentを取り出したい、みたいな時使えそうかなと思ってます。

```go:main.go
func main() {
	doc1 := Document{Name: "doc1", Status: Draft}
	doc2 := Document{Name: "doc2", Status: Draft}
	doc3 := Document{Name: "doc3", Status: Published}
	doc4 := Document{Name: "doc4", Status: Published}
	docs := []Document{doc1, doc2, doc3, doc4}
	fs := slices.FilterSlice(docs, func(t Document) bool {
		return t.Status.IsPublished()
	})
	fmt.Printf("公開中のdocumentは%+v", fs) // 公開中のdocumentは[{Name:doc3 Status:1} {Name:doc4 Status:1}]
}

type Document struct {
	Name   string
	Status DocStatus
}
type DocStatus int

const (
	Draft DocStatus = iota
	Published
)

func (ds DocStatus) IsPublished() bool {
	return ds == Published
}
```

## sliceの要素を削除する
mapはdelete関数あるのに、sliceにはないのか。。と思い作りました笑
```go
func DeleteValue[T comparable](slice []T, target T) []T {
	resultSlice := []T{}
	for _, v := range slice {
		if v != target {
			resultSlice = append(resultSlice, v)
		}
	}
	return resultSlice
}
```

使い方はシンプルでvalueを指定して、その要素を削除します。
```go:main.go
func main() {
	is := []int{1, 2, 3, 4}
	ds := slices.DeleteValue(is, 3)
	fmt.Println(ds) // [1, 2, 4]
}
```

## おわり
最後まで読んでいただきありがとうございました！
1つでもこれ使えそう！と思ってもらえたら嬉しいです！

一応githubでもソースコード公開してます。
https://github.com/hiroaki-u/go-pkg