LoginSignup
107
64

More than 1 year has passed since last update.

go言語のslice操作をまとめてみた(shiftしたりpushしたり)

Last updated at Posted at 2015-10-13

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"}

サンプル GoPlayground

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, 追加要素)

サンプル GoPlayground

sliceの末尾を取り出す

pop動作です。末尾を取り出してから、末尾を除いた範囲を切り出しています。
参照のみの変更なので、ポインタは変わりません。

// 要素の取り出しはポインタでなく値参照してください。
// (*をつける、copyするなど)
// 他のポインタ変数が残っていると書き換えられる可能性があるので…
末尾の要素 := slice[len(slice)-1] 
slice = slice[:len(slice)-1] // 末尾削除

サンプル GoPlayground

sliceの先頭に要素を追加する

unshift動作です。スライスの要素を右にシフトして先頭要素を置き換えています。
自身のappendなのでcapacityに余裕があればポインタは変わりません。

slice, slice[0] = append(slice[:1], slice[0:]...), 追加要素

サンプル GoPlayground

sliceの先頭から要素を取り出す

shift動作です。先頭の要素を取り出してから、2番目以降の要素を切り出します。
ポインタの先頭は変わりますが、各要素のポインタは変わりません。

// 要素の取り出しはポインタでなく値参照してください。
// (*をつける、copyするなど)
// 他のポインタ変数が残っていると書き換えられる可能性があるので…
先頭の要素 := slice[0]
slice = slice[1:]

サンプル GoPlayground

任意の場所に要素を追加する

特殊な場合だとは思いますが…
先頭に追加する場合の応用です。追加する位置より後ろを右側にシフトして、空いた場所に値を入れています。
もちろんポインタは変わりません。

pos := 追加する位置
// NGコード 代入順序が保証されないからダメでした
// slice , slice[pos] = append(slice[:pos+1], slice[pos:]...), 追加する要素
slice = append(slice[:pos+1], slice[pos:]...)
slice[pos] = 追加する要素

サンプル GoPlayground

任意の場所から取り除く

pos := 取り出す位置
slice = append(slice[:pos], slice[pos+1:]...)

サンプル GoPlayground

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

サンプル GoPlayground

reflect.DeepEqual
サンプル GoPlayground

よりスマートな方法ご存じであればぜひ教えて下さいm(_ _)m

ちなみに2次元スライスはこちら
https://qiita.com/egnr-in-6matroom/items/859559296f8e78bffd34

さらに詳しい動きはソースを読むこと。すべてgoのコードで語ってくれるのがgoの良いところ…

107
64
3

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
107
64