結論
slice = append(slice[:idx], slice[idx+1:]...)
上記のようにできない場合で、どうしても元のスライスを保持したいときはmake©しましょう
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
newSlice := make([]int, len(slice))
copy(newSlice, slice)
はじめに
スライスを使うときに無意識でappendを使うかと思います。
単純に追加するだけであれば、至って普通の挙動を示しますが、とある条件の時に
想定しない挙動になることがあります。
実験内容
配列の中から特定のインデックスにある値を削除する処理です。
例えば以下のようなコードがあった場合はどうでしょうか?
main.go
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(slice)
idx := 5
for k, v := range slice {
if k == idx {
fmt.Println("before:", slice[k], v) //5 5
fmt.Println("append", append(slice[:idx], slice[idx+1:]...)) //特定の配列を削除する
fmt.Println("original slice", slice)
fmt.Println("after", slice[k], v) // この出力は?
}
}
}
普通は
slice = append(slice[:idx], slice[idx+1:]...)
のようにするところですが、例えば以下のようなケースが想定されます。
- 共通化するべく、別メソッドで作る
- 削除する前と後を両方保持しておきたい
感覚的にはbeforeとafterは全く同じになると思うかもしれません。
#実験結果
実際に実行してみます
[0 1 2 3 4 5 6 7 8 9]
before: 5 5
append [0 1 2 3 4 6 7 8 9]
original slice [0 1 2 3 4 6 7 8 9 9]
after 6 5
afterの値が「5 5」ではなく「6 5」となります。元のスライスもおかしくなっていることがわかります。
なぜ普遍だと錯覚していた?
A Tour of Go より
もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直します。 その場合、戻り値となるスライスは、新しい割当先を示すようになります。
つまり言い換えるとappendの引数にあるスライスの容量が新たに割り当てるまでもなく大きい場合は割り当て直さない。
append にで指定した元の配列が変更されてしまい、
感覚と反する結果になっています