sliceの動作はそこそこ分かりやすいけれども、操作は煩雑。
自分が使ってる方法をよく忘れるのでとりあえずまとめてみました。
best practiceを求めたわけでは無いですが、一時変数など、余計なメモリ確保をしないようにはしてみました。
パフォーマンス検証とかはそのうちやります。
本家のwikiにslice trickあるのでそちらのほうも参考に。
#slice初期化
コンポジットリテラル表記
composite literalで直接初期値を記述しています。
この場合、capacityは初期化したときの要素数となります。
// 整数のスライス
// len: 5, cap: 5
slice1 := []int{1, 2, 3, 4, 5}
// 文字列のスライス
// len: 3, cap: 3
slice2 := []string{"str1", "str2", "str3"}
make関数
make functionで初期化します。
スライスの長さ(length)と確保するメモリ(capacity)をそれぞれ指定できます。
当たり前ですが length ≦ capacity
です。
スライスのみ指定するとcapacity = length
になります。
数値は要素を0、文字列は要素を空文字列、ポインタはnilとして初期化するようです。
// lengthを指定
slice1 := make([]int, 5)
// slice1 = [0, 0, 0, 0, 0]
// length, capacity指定
slice2 := make([]int, 5, 10)
// slice2 = [0, 0, 0, 0, 0]
// length=10(先頭からindex10の前)でスライスすると、capacityある10要素目まで初期化されている
// slice2[:10] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// []stringの場合
slice3 := make([]string, 5)
// slice3 = ["", "", "", "", ""]
// ポインタの場合
type myType struct {}
slice4 := make([]myType, 5)
// slice4 = [nil, nil, nil, nil, nil]
#slice基本操作
インデックスで要素アクセス
lengthの範囲内でインデックスを指定して要素を参照します。
capacityの範囲内であっても、lengthの範囲外であればエラーになります。
slice := []int{1, 2, 3}
fmt.Printf("%d\n", slice[0])
// 範囲外のインデックス指定はindex out of range [3]でエラーになる
// fmt.Printf("%d\n", slice[3])
sliceの一部を切り取り(スライス)
capacityの指定などでメモリ確保された範囲内であれば、指定した範囲を参照してスライスとして利用できます。
参照なので要素を変更すると元のスライスにも反映されるので注意。
範囲の末尾は、末尾のインデックス1つ後を指定します。(一見わかりにくいですが、末尾+1=先頭+長さになるので実際のコーディングでは扱いやすい)
// 初期スライス
// インデックス 0 1 2 3 4
slice := []int{1, 2, 3, 4, 5}
// sliceから インデックス1〜3の範囲を切り取り
slice2 := slice[1:4]
// slice2 = [2 3 4]
// sliceから切り取っているのでslice2[0] と slice[1]は同じポインタ
// sliceから インデックス2〜末尾までを切り取り
slice3 := slice[2:]
// slice3 = [3, 4, 5]
// sliceから 先頭〜インデックス2まで切り取り
slice4 := slice[:2]
// slice4 = [1, 2]
#slice応用操作
##sliceの末尾に要素を追加する
push動作です。ふつうにappendしているだけです。
slice = append(slice, 追加要素)
##sliceの末尾を取り出す
pop動作です。末尾を取り出してから、末尾を除いた範囲を切り出しています。
参照のみの変更なので、ポインタは変わりません。
// 要素の取り出しはポインタでなく値参照してください。
// (*をつける、copyするなど)
// 他のポインタ変数が残っていると書き換えられる可能性があるので…
末尾の要素 := slice[len(slice)-1]
slice = slice[:len(slice)-1] // 末尾削除
sliceの先頭に要素を追加する
unshift動作です。スライスの要素を右にシフトして先頭要素を置き換えています。
自身のappendなのでcapacityに余裕があればポインタは変わりません。
slice, slice[0] = append(slice[:1], slice[0:]...), 追加要素
sliceの先頭から要素を取り出す
shift動作です。先頭の要素を取り出してから、2番目以降の要素を切り出します。
ポインタの先頭は変わりますが、各要素のポインタは変わりません。
// 要素の取り出しはポインタでなく値参照してください。
// (*をつける、copyするなど)
// 他のポインタ変数が残っていると書き換えられる可能性があるので…
先頭の要素 := slice[0]
slice = slice[1:]
##任意の場所に要素を追加する
特殊な場合だとは思いますが…
先頭に追加する場合の応用です。追加する位置より後ろを右側にシフトして、空いた場所に値を入れています。
もちろんポインタは変わりません。
pos := 追加する位置
// NGコード 代入順序が保証されないからダメでした
// slice , slice[pos] = append(slice[:pos+1], slice[pos:]...), 追加する要素
slice = append(slice[:pos+1], slice[pos:]...)
slice[pos] = 追加する要素
##任意の場所から取り除く
pos := 取り出す位置
slice = append(slice[:pos], slice[pos+1:]...)
slice比較
愚直にやるなら1要素ずつ比較します。
reflect.DeepEqualですべての要素を比較する手段もよく見かけますが、内部構造がある場合は注意してください。
[]byteはbytes.Equalsも使えます。
slice1 := []int{1, 2, 3}
slice2 := []int{1, 2, 3}
cmp := true
for i, e := range slice1 {
if e!= slice2[i] {
cmp := false
break
}
}
// cmp == true
// 要素がintなどの型はこれでも可。structなどであれば予期せぬ比較がされる可能性があるので要注意。
cmp := reflect.DeepEqual(slice1, slice2) // true
// byte はbytes.Equalsが使えます。
bytes.Equal([]byte{1,2,3,4}, []byte{1,2,3,4}) // true
reflect.DeepEqual
サンプル GoPlayground
よりスマートな方法ご存じであればぜひ教えて下さいm(_ _)m
ちなみに2次元スライスはこちら
https://qiita.com/egnr-in-6matroom/items/859559296f8e78bffd34
さらに詳しい動きはソースを読むこと。すべてgoのコードで語ってくれるのがgoの良いところ…