⛏️

Go 1.22でslicesパッケージに追加された便利関数

2024/02/14に公開

こんにちは。ついにGo 1.22がリリースされましたね!
1.22のリリースではforの変更に注目が集まりがちですが、標準ライブラリであるslicesパッケージにも地味に便利な関数が追加されています。(Release note)
この記事では、今回slicesパッケージに追加されたConcatDeleteInsertを紹介します。

Concat

func Concat[S ~[]E, E any](slices ...S) S
Concat returns a new slice concatenating the passed in slices.

渡されたスライスを結合して、新たなスライスとして返します。
複数のスライスを1回で結合できる点が便利だと思いました。
今まで、複数のスライスを結合する場合は以下のように書いていました。

slice1 := []int64{1, 2}
slice2 := []int64{3, 4}
slice3 := []int64{5, 6}
slice4 := []int64{7, 8}

slice1 = append(slice1, slice2...)
slice1 = append(slice1, slice3...)
slice1 = append(slice1, slice4...)
// or slice1 = append(append(append(slice1, slice2...), slice3...), slice4...)

fmt.Print(slice1) // [1 2 3 4 5 6 7 8]

これをConcatを使うと、以下のように書くことができます。

slice1 := []int64{1, 2}
slice2 := []int64{3, 4}
slice3 := []int64{5, 6}
slice4 := []int64{7, 8}

newSlice := slices.Concat(slice1, slice2, slice3, slice4)
fmt.Print(newSlice) // [1 2 3 4 5 6 7 8]

何度もappendする必要がなくなり、スッキリと直感的に書けるようになりました!
個人的に、テストコードの中で何度もappendしたことがあったので、この関数の追加は嬉しいです。

Delete

func Delete[S ~[]E, E any](s S, i, j int) S
Delete removes the elements s[i:j] from s, returning the modified slice.
Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
Delete is O(len(s)-i), so if many items must be deleted,
it is better to make a single call deleting them all together than to delete one at a time.
Delete zeroes the elements s[len(s)-(j-i):len(s)].

スライスからi:jで指定された要素を削除し、変更されたスライスを返します。
指定されたjがスライスの最大長より大きかったり、指定された範囲が存在しない場合はpanicを起こします。
また、大量の要素を削除する場合は、1つずつ削除するよりもまとめて一気に削除した方が効率的だということが述べられています。
この関数により、appendcopyを使わずにスライスの先頭や末尾の要素の削除を直感的に書けるようになったように思います。(Deleteの中ではappendを使っているようです)

先頭の要素を削除する場合

digits := []int64{0, 1, 2, 3, 4}
digits = slices.Delete(digits, 0, 1)

// slice: [1 2 3 4], len: 4, cap: 5
fmt.Printf("slice: %v, len: %d, cap: %d", digits, len(digits), cap(digits))

末尾の要素を削除する場合

digits := []int64{0, 1, 2, 3, 4}
digits = slices.Delete(digits, len(digits)-1, len(digits))

// slice: [0 1 2 3], len: 4, cap: 5
fmt.Printf("slice: %v, len: %d, cap: %d", digits, len(digits), cap(digits))

capacityは渡したスライスと同じになるようです。

Insert

func Insert[S ~[]E, E any](s S, i int, v ...E) S
Insert inserts the values v... into s at index i, returning the modified slice.
The elements at s[i:] are shifted up to make room. In the returned slice r,
r[i] == v[0], and r[i+len(v)] == value originally at r[i].
Insert panics if i is out of range. This function is O(len(s) + len(v)).

指定したスライスのi番目に要素を挿入し、変更されたスライスを返します。
挿入位置以降の要素は、挿入された要素分、右へシフトされます。
指定したインデックスが存在しない場合はpanicを起こします。

これもDeleteと同じような理由ですが、スライスの先頭に要素を追加する際にappendを使わずスッキリした書き方ができるようになったのではないかと思います。

digits := []int64{1, 2, 3}
digits = slices.Insert(digits, 0, 0) // 先頭に追加
fmt.Println(digits) // [0 1 2 3]

digits = slices.Insert(digits, len(digits), 4) // 末尾に追加
fmt.Println(digits) // [0 1 2 3 4]

おわりに

slicesパッケージはGo 1.21から追加された、かなり新しいパッケージで、スライス操作をよりシンプルで効率的にする関数を提供してくれています。
今までは外部ライブラリに頼っていたりしましたが、標準ライブラリであるslicesパッケージを使って、コードの可読性とパフォーマンスを向上させましょう!

Discussion