[译]Go Slice 秘籍

这是 Golang官方的一个总结: SliceTricks

由于引入了内建的append的方法, 包container/vector的很多方法都被移除了,可以被内建的appendcopy方法代替。

下面是栈vector的操作方法的实现,使用slice实现相关的操作。

AppendVector

1
a = append(a, b...)

Copy

1
2
3
4
b = make([]T, len(a))
copy(b, a)
// 如果a不为空,也可以用下面的方式
b = append([]T(nil), a...)

Cut

切掉一段数据

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

Delete

删除一个元素

1
2
3
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]

Delete,但不保持原来顺序

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

注意:如果元素是一个指针,或者是一个包含指针字段的struct,上面的cutdelete实现可能会有潜在的内存泄漏的问题。一些元素的值可能会被a一直引用而不被释放,下面的代码可以解决这个问题。

Cut

1
2
3
4
5
copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {
a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]

Delete

1
2
3
copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]

Delete,但不保持原来顺序

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

Expand

插入一段到中间

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

Extend

插入一段到尾部

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

Insert

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

注意: 第二个append会使用它底层的存储创建一个新的slice,然后复制a[i:]到这个slice,然后把这个slice再复制回s。 新的slice的创建和第二次copy可以使用下面的方式来避免:

Insert

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

InsertVector

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

Pop

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

Push

1
a = append(a, x)

Shift

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

Unshift

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

其它技巧

无额外对象分配的filter

这个技巧利用了slice会共享它的底层的数据存储和容量。

1
2
3
4
5
6
b := a[:0]
for _, x := range a {
if f(x) {
b = append(b, x)
}
}

反转

将slice中的元素反转。

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

下面的代码类似,只不过使用了两个索引变量

1
2
3
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
a[left], a[right] = a[right], a[left]
}