在GO语言中删除和添加数组元素

46

我有两个数组声明为:var input []stringvar output []string

初始时,输入数组被填充了一些ID,输出数组为空。

每次迭代后,我想从输入数组中随机删除一个元素并将其添加到输出数组中。

最终,输出数组中的所有元素将与输入数组相同(但索引顺序不同)。

for index := 0; index < len(input); index++ {
    if !visited[index] {
        //do something
    }
}
output[#iteration index] = input[current index]

当我尝试这样做时,遇到了数组越界错误

3个回答

52

对于output数组,您需要使用append或者分配一个具有与input相同大小的初始容量。

// before the loop
output := make([]string, len(input))

我的建议是使用reserve,因为append会导致很多不必要的重新分配,并且你已经知道需要的容量,因为它基于input

另一件事是:

output = append(output, input[index])

但就像我之前所说的,从我观察到的情况来看,append() 方法会按指数增长初始容量。如果你没有指定任何值,这将是以2为底数,这意味着你要做几次不必要的重新分配才能达到所需的容量。


2
有没有一种方法可以在不重新分配的情况下将元素添加到切片中? - Alexander Mills

43
你可以在golang/SliceTricks中找到一些有用的技巧。
自从引入了append内置函数后,大部分container/vector包的功能已被移除,现在可以使用appendcopy来实现它们。
以下是vector方法及其切片操作的类比:
a = append(a, b...)

b = make([]T, len(a))
copy(b, a)
// or
b = append([]T(nil), a...)

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

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

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

注意 如果元素的类型是指针或具有需要进行垃圾回收的指针字段的结构体,则上述的CutDelete实现存在潜在的内存泄漏问题:一些具有值的元素仍然被切片a引用,因此无法进行回收。以下代码可以解决这个问题:

Cut

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]

删除

copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
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:]...)...)

注意 第二个append会创建一个新的切片并拥有自己的底层存储,将a[i:]中的元素复制到该切片中,然后这些元素将被复制回到切片a(由第一个append完成)。通过使用另一种方式可以避免创建新的切片(因此避免内存垃圾)和第二次复制:

插入

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

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

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

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

a = append(a, x)

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

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

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

额外技巧

无需分配内存的过滤方法

这个技巧利用了切片与原始数组共享同一后备数组和容量的特性,因此可以重复使用已有的存储空间来创建过滤后的切片。当然,原始内容会被修改。

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

相同的事情,只是有两个索引:
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
    a[left], a[right] = a[right], a[left]
}

洗牌

Fisher-Yates算法:

for i := len(a) - 1; i > 0; i-- {
    j := rand.Intn(i + 1)
    a[i], a[j] = a[j], a[i]
}

我有一个结构体数组,其中包含两个属性(例如 [{ "prop1": 1, "prop2": 2 }, { "prop1": 1, "prop2": 2 }] 等)。我想从这个现有的数组中仅为 prop1 创建一个新的 int 数组 - 最好的方法是什么?(你可以可能添加一个例子吗?) - Rob McCabe

1
在 Go 1.21 中,Slice 提供了 InsertDelete(以及更多!)函数。

示例

Insert

package main

import (
    "fmt"
    "slices"
)

func main() {
    names := []string{"Alice", "Bob", "Vera"}
    names = slices.Insert(names, 1, "Bill", "Billie")
    names = slices.Insert(names, len(names), "Zac")
    fmt.Println(names)
}

删除

package main

import (
    "fmt"
    "slices"
)

func main() {
    letters := []string{"a", "b", "c", "d", "e"}
    letters = slices.Delete(letters, 1, 4)
    fmt.Println(letters)
}

Gasket.go:5:1: 在以下任何位置都找不到“slices”包: /usr/lib/go-1.11/src/slices(来自$GOROOT) /root/go/src/slices(来自$GOPATH) - Owl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接