1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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

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

slices

Contains

スライスに特定の要素が存在するかを判定します。

func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Contains(s, "orange")) // true
    fmt.Println(slices.Contains(s, "banana")) // false
}
  • 従来の実装例

(直感的なコードですが、パフォーマンス面は度外視しているのでスライスのサイズが大きいと線形的に処理時間が増えます)

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

スライスの任意の位置に要素を追加します。

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]
}
  • 従来の実装例
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

スライス内の指定した範囲の要素を削除します。

func main() {
	s := []string{"apple", "orange", "grape", "banana"}
	fmt.Println(slices.Delete(s, 1, 3)) // [apple banana]
}
  • 従来の実装例
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

指定した要素のインデックスを返します。

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

Deleteと組み合わせると、スライスから任意の要素を削除できます。

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]
	}
}
  • 従来の実装例
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
}

任意の要素の値で削除する実装例は以下の通りです。

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になります。

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でもともと比較可能だったのであまりありがたみはないですね。

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の場合は辞書順で一番最後になる要素を返します。

func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Max(s)) // orange
}
  • 従来の実装例
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の場合は辞書順で一番最初になる要素を返します。

func main() {
	s := []string{"apple", "orange", "grape"}
	fmt.Println(slices.Min(s)) // apple
}
  • 従来の実装例
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の場合は辞書順にソートします。

func main() {
	s := []string{"apple", "orange", "grape"}
	slices.Sort(s)
	fmt.Println(s) // [apple grape orange]
}
  • 従来の実装例

sortパッケージで実装できます。
基本的にはクイックソートで、要素数によってアルゴリズムを変えているみたいなのでパフォーマンスも悪くなさそうです。

func main() {
	s := []string{"apple", "orange", "grape"}
	sort.Sort(sort.StringSlice(s))
	fmt.Println(s) // [apple grape orange]
}

Reverse

スライス内の要素を降順にします。stringの場合は辞書順の逆からソートします。

func main() {
	s := []string{"apple", "orange", "grape"}
	slices.Reverse(s)
	fmt.Println(s) // [grape orange apple]
}
  • 従来の実装

こちらもsort.Sortと同様に既存パッケージで実装できます。

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同士の全ての要素が同じか比較します。
こちらもスライスと同様、「等値」ではなく「等価」としての判定となります。

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
}
  • 従来の実装例
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にコピーします。

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]
}
  • 従来の実装例
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操作を自前で用意することが少なくなり、よりビジネスロジックの実装に集中できるようになりますね。

ではまた。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?