Go言語はシンプルで効率的なプログラミング言語であり、スライスはその中でも重要なデータ構造です。
PHPでは配列関数が豊富に用意されており、その多くはスライスの操作を容易にします。
本記事では、Go言語でPHPのいくつかの配列関数を再現する方法について解説します。
スライスの基本操作
// スライスの作成
slice := make([]int, 0, 6)
slice = append(slice, 1, 2, 3, 4)
// 最後に要素の追加
slice = append(slice, 5)
fmt.Println(slice) // [1, 2, 3, 4, 5]
// 指定位置に要素追加
index := 1
addValue := 9
slice = append(slice[:index], append([]int{addValue}, slice[index:]...)...)
fmt.Println(slice) // [1, 9, 2, 3, 4, 5]
// 特定の要素の削除
slice = append(slice[:index], slice[index+1:]...)
fmt.Println(slice) // [1, 2, 3, 4, 5]
// 最後の要素の削除
slice = slice[:len(slice)-1]
fmt.Println(slice) // [1, 2, 3, 4]
// 要素のアクセス
value := slice[index]
fmt.Println(value) // 2
スライスの容量と長さ
スライスは容量(capacity)と長さ(length)という2つのパラメータを持ちます。
容量はスライスが格納できる要素数を表し、長さはスライスが現在保持している要素数を表します。
内部配列のキャパシティを超える要素数が追加されると、Goランタイムはより大きいキャパシティの新しい内部配列を割り当て、既存の要素をそちらにコピーします。
そのため、予め十分なキャパシティを持つスライスを作成しておくことで、要素の追加や操作の性能を向上させることができます。
slice := []int{1, 2, 3}
fmt.Println(cap(slice))
fmt.Println(len(slice))
PHP配列関数を再現する
array_push
一つ以上の要素を配列の最後に追加する。
func arrayPush[T any](slice []T, elements ...T) []T {
return append(slice, elements...)
}
利用例
slice := []int{1, 2, 3}
slice = arrayPush(slice, 4, 5)
fmt.Println(slice) // [1, 2, 3, 4, 5]
array_pop
配列の末尾の要素を取り除く。
PHPでは配列が空の時にnullを返しますが、golangではnilを返すことができないため、ゼロ値を返しています。
func arrayPop[T any](slice *[]T) T {
if len(*slice) == 0 {
var zeroValue T
return zeroValue
}
lastIndex := len(*slice) - 1
poppedElement := (*slice)[lastIndex]
*slice = (*slice)[:lastIndex]
return poppedElement
}
利用例
// 利用例
slice := []string{"orange", "banana", "apple", "raspberry"}
fruit := arrayPop(&slice)
fmt.Println(slice) // [orange, banana, apple]
fmt.Println(fruit) // raspberry
array_shift
配列の先頭から要素を一つ取り出す。
func arrayShift[T any](slice *[]T) T {
if len(*slice) == 0 {
var zeroValue T
return zeroValue
}
poppedElement := (*slice)[0]
*slice = (*slice)[1:]
return poppedElement
}
利用例
// 利用例
slice := []string{"orange", "banana", "apple", "raspberry"}
fruit := arrayShift(&slice)
fmt.Println(slice) // [banana apple raspberry]
fmt.Println(fruit) // orange
array_merge
ひとつまたは複数の配列をマージする。
func arrayMerge[T any](slices ...[]T) []T {
merged := []T{}
for _, slice := range slices {
merged = append(merged, slice...)
}
return merged
}
利用例
// 利用例
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
mergedSlice := arrayMerge(slice1, slice2)
fmt.Println(mergedSlice) // [1, 2, 3, 4, 5, 6]
array_chunk
配列を分割する。
func arrayChunk[T any](slice []T, size int) [][]T {
length := len(slice)
chunks := int(math.Ceil(float64(length) / float64(size)))
result := make([][]T, 0, chunks)
for i := 0; i < chunks; i++ {
start := i * size
end := start + size
if end > length {
end = length
}
result = append(result, slice[start:end])
}
return result
}
利用例
// 利用例
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := arrayChunk(numbers, 3)
fmt.Println(result) // [[1, 2, 3] [4, 5, 6] [7, 8, 9] [10]]
まとめ
Golangではデフォルト引数や、返り値にnull許容の戻り値を返すことができない等の言語的な特徴がありますが、スライスを活用することで、Go言語でも便利な配列操作を実現することができます。
おまけ
参考資料: https://zenn.dev/mattn/articles/31dfed3c89956d
良い記事があったため、共有。
appendにより、スライスを削除する方法の場合、消したインデックス以降の要素を前にずらす必要があり、先頭にいく程、削除にかかる時間は長くなるようです。
そのため、削除後、スライス順を気にしないケースでは下記の記述方法が良いと解説されています。
a[i] = a[len(a)-1]
a = a[:len(a)-1]
上記で何をしているのか、パッと見、理解が難しかったため、下記にコードを記載しました。
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
index := 3
numbers[index] = numbers[len(numbers)-1]
fmt.Println(numbers) // [1, 2, 3, 10, 5, 6, 7, 8, 9, 10]
numbers = numbers[:len(numbers)-1]
fmt.Println(numbers) // [1, 2, 3, 10, 5, 6, 7, 8, 9]
削除要素の場所を末尾の値で置き換えて、末尾の要素を削除しているようです。
確かにこれであれば、スライスを前にずらす必要がなくなり、より高速に処理できます。