Go Slice Tricks (Japanese)

"SliceTricks" from golang wiki

Go公式WikiのSliceTricksの日本語意訳になります。
(元記事の最終更新日: 2017/06/29 rev.23)

スライスを扱う上で有用なテクニックがまとまっています。


スライス・トリック

container/vector パッケージの機能はGoのバージョンが1になり削除されましたが、代わりに組み込みのappendcopyに置き換えられました。

ベクトルとスライス操作は以下のようになります。

ベクトルの追加

a = append(a, b...)

コピー

b = make([]T, len(a))
copy(b, a)

// または以下の方法でも可
b = append([]T(nil), a...)

部分カット

a = append(a[:i], a[j:]...)

訳注: 例えばスライスの先頭の要素10個と末尾の要素10個を取得したい場合は以下のようになります
Play Groundで確認

size := len(a)
a = append(a[:10], a[(size-10):]...)

削除

a = append(a[:i], a[i+1:]...)

// または以下の方法でも可
a = a[:i+copy(a[i:], a[i+1:])]

削除(元の順番を保証しない)

a[i] = a[len(a)-1]
a = a[:len(a)-1]

~注~ もし要素の型がポインタ型やポインタのフィールドを含む構造体の場合、ガベージコレクション(GC)が必要になるため、上記の部分カットや削除の実装は潜在的なメモリリークの問題があります。値を持つ要素はスライスからの参照が残っており、GCの対象になりません。この問題を修正したコードは以下になります。

部分カット

copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {
	a[k] = nil // nil or この型のゼロ値を代入する
}
a = a[:len(a)-j+i]

削除

copy(a[i:], a[i+1:])
a[len(a)-1] = nil // nil or この型のゼロ値を代入する
a = a[:len(a)-1]

削除(元の順番を保証しない)

a[i] = a[len(a)-1]
a[len(a)-1] = nil
a = a[:len(a)-1]

拡張(途中追加)

a = append(a[:i], append(make([]T, j), a[i:]...)...)

拡張(末尾追加)

a = append(a, make([]T, j)...)

挿入

a = append(a[:i], append([]T{x}, a[i:]...)...)

~注~ 2番目の append は、自身の領域を持つ新しいスライスを作成し、その新しいスライスにa[i:]の要素をコピーします。そしてこれらの要素は(1番目の append で) 元のスライス a へとコピーされます。新しいスライスの作成(とそれによるメモリガベージ)と2番目のコピーは他の方法で回避することもできます。

挿入

s = append(s, 0)
copy(s[i+1:], s[i:])
s[i] = x

ベクトルの挿入

a = append(a[:i], append(b, a[i:]...)...)

Pop(末尾からの取出し)

x, a = a[len(a)-1], a[:len(a)-1]

Shift, Pop Back(先頭からの取出し)

x, a = a[0], a[1:]

Push(末尾への追加)

a = append(a, x)

Unshift, Push Back(先頭への追加)

a = append([]T{x}, a...)

応用トリック

メモリ割り当てをせずにフィルタリングする

このトリックは、スライスがその背後にある配列と元の容量(capacity)を共有するという事実を使い、その領域はフィルタ済みスライスによって再利用されます。もちろん、元の内容は変更されます。

b := a[:0]
for _, x := range a {
	if f(x) {
		b = append(b, x)
	}
}

逆順

以下のコードでは、スライスの内容を同じ要素のまま逆の順番にします。

for i := len(a)/2-1; i >= 0; i-- {
	opp := len(a)-1-i
	a[i], a[opp] = a[opp], a[i]
}

2つの配列インデックスを使うことでも同様のことができます。

for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
	a[left], a[right] = a[right], a[left]
}
 
comments powered by Disqus