乐闻世界logo
搜索文章和话题

Golang 如何从数组中删除元素?

7 个月前提问
3 个月前修改
浏览次数158

6个答案

1
2
3
4
5
6

在 Go 语言中,数组(Array)是一种固定长度的数据结构,因此你不能直接从数组中删除元素。然而,你可以使用切片(Slice)来模拟这个行为。切片是一种可变长度的数组抽象。

要从切片中删除特定位置的元素,你有几个选择:

  1. 使用 append 和切片操作符:你可以使用两次切片和 append 函数将要删除的元素之前和之后的元素连接起来。这个操作不会影响原始的数组,但原始的切片会因为 append 而改变。

    go
    package main import "fmt" func main() { // 初始切片 a := []int{1, 2, 3, 4, 5} // 删除索引为 2 的元素(即元素 3) a = append(a[:2], a[3:]...) fmt.Println(a) // 输出:[1 2 4 5] }

    在这个例子中,a[:2] 创建了一个包含元素 12 的新切片,a[3:] 创建了一个包含元素 45 的新切片。append 函数将这两个切片连接起来,形成一个新的切片,其中不包含元素 3

  2. 使用 copy:如果你想保持原始切片不变,可以使用 copy 函数。这种方法会将删除元素之后的元素向前复制一位。

    go
    package main import "fmt" func main() { a := []int{1, 2, 3, 4, 5} // 删除索引为 2 的元素 copy(a[2:], a[3:]) a = a[:len(a)-1] // 缩减切片的长度 fmt.Println(a) // 输出:[1 2 4 5] }

    在这个例子中,copy(a[2:], a[3:]) 将切片中索引为 34 的元素复制到索引为 23 的位置,然后通过缩减切片的长度来丢弃最后一个元素。

需要注意的是,这些操作对基础数组的影响取决于切片的容量和长度。在一些情况下,为避免修改原始数组,可能需要先复制切片。而且,对于大型数据集,这些操作可能会引起性能问题,因为它们涉及到复制许多元素。

在进行删除操作时,通常还需要考虑内存泄漏的问题,尤其是当切片中包含指针或者其他需要垃圾收集的数据结构时。在这种情况下,在进行删除操作后可能需要清除无用的引用:

go
a := []int{1, 2, 3, 4, 5} index := 2 copy(a[index:], a[index+1:]) a[len(a)-1] = 0 // 先清零或者设置为nil a = a[:len(a)-1] fmt.Println(a) // 输出:[1 2 4 5]

这个操作将 a[index] 之后的所有元素向前移动一位,并在移动后将最后一个元素设置为默认值(对于整数是 0,对于指针是 nil),以防止潜在的内存泄漏。然后,它会缩减切片的长度,从而移除了最后一个元素。

2024年6月29日 12:07 回复

订单事宜

如果要保持数组有序,则必须将删除索引右侧的所有元素向左移动一位。希望这可以在 Golang 中轻松完成:

shell
func remove(slice []int, s int) []int { return append(slice[:s], slice[s+1:]...) }

然而,这是低效的,因为您最终可能会移动所有元素,而成本高昂。

顺序并不重要

如果您不关心顺序,则可以更快地将要删除的元素替换为切片末尾的元素,然后返回 n-1 个第一个元素:

shell
func remove(s []int, i int) []int { s[i] = s[len(s)-1] return s[:len(s)-1] }

使用重新切片方法,清空 1 000 000 个元素的数组需要 224 秒,而使用此方法只需 0.06 纳秒。

此答案不执行边界检查。它需要一个有效的索引作为输入。这意味着大于或等于初始值的负值或索引len(s)将导致 Go 恐慌。

切片和数组的索引为 0,删除数组的第 n 个元素意味着提供输入n-1。要删除第一个元素,请调用remove(s, 0),要删除第二个元素,请调用remove(s, 1),依此类推。

2024年6月29日 12:07 回复

这看起来有点奇怪,但这里的大多数答案都是危险的,并且掩盖了他们实际在做什么。查看关于从切片中删除项目的原始问题,正在制作切片的副本,然后将其填充。这可以确保当切片在程序中传递时不会引入细微的错误。

下面是一些代码,比较用户在此线程中的答案和原始帖子。这是一个可以在其中乱搞此代码的go 游乐场。

基于附加的删除

shell
package main import ( "fmt" ) func RemoveIndex(s []int, index int) []int { return append(s[:index], s[index+1:]...) } func main() { all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] removeIndex := RemoveIndex(all, 5) fmt.Println("all: ", all) //[0 1 2 3 4 6 7 8 9 9] fmt.Println("removeIndex: ", removeIndex) //[0 1 2 3 4 6 7 8 9] removeIndex[0] = 999 fmt.Println("all: ", all) //[999 1 2 3 4 6 7 9 9] fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9] }

在上面的示例中,您可以看到我创建了一个切片并手动用数字 0 到 9 填充它。然后我们从所有切片中删除索引 5 并将其指定为删除索引。然而,当我们现在打印出所有内容时,我们发现它也已被修改。这是因为切片是指向底层数组的指针。将其写出也会removeIndex导致all被修改,区别在于,all有一个元素不再可以从 访问removeIndex。接下来我们更改一个值removeIndex,我们可以看到all它也被修改了。《Effective go》对此进行了更详细的介绍。

我不会详细介绍下面的示例,但它对我们的目的做了同样的事情。这只是说明使用副本没有什么不同。

shell
package main import ( "fmt" ) func RemoveCopy(slice []int, i int) []int { copy(slice[i:], slice[i+1:]) return slice[:len(slice)-1] } func main() { all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] removeCopy := RemoveCopy(all, 5) fmt.Println("all: ", all) //[0 1 2 3 4 6 7 8 9 9] fmt.Println("removeCopy: ", removeCopy) //[0 1 2 3 4 6 7 8 9] removeCopy[0] = 999 fmt.Println("all: ", all) //[99 1 2 3 4 6 7 9 9] fmt.Println("removeCopy: ", removeCopy) //[999 1 2 3 4 6 7 8 9] }

问题原答案

查看原始问题,它不会修改从中删除项目的切片。对于大多数访问此页面的人来说,使该线程中的原始答案成为迄今为止最好的答案。

shell
package main import ( "fmt" ) func OriginalRemoveIndex(arr []int, pos int) []int { new_arr := make([]int, (len(arr) - 1)) k := 0 for i := 0; i < (len(arr) - 1); { if i != pos { new_arr[i] = arr[k] k++ } else { k++ } i++ } return new_arr } func main() { all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] originalRemove := OriginalRemoveIndex(all, 5) fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] fmt.Println("originalRemove: ", originalRemove) //[0 1 2 3 4 6 7 8 9] originalRemove[0] = 999 fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] fmt.Println("originalRemove: ", originalRemove) //[999 1 2 3 4 6 7 8 9] }

正如您所看到的,此输出符合大多数人的预期,也可能是大多数人想要的。的修改originalRemove不会引起变化,all删除索引并分配索引的操作也不会引起变化!极好的!

这段代码有点长,所以上面的内容可以改为这样。

正确答案

shell
package main import ( "fmt" ) func RemoveIndex(s []int, index int) []int { ret := make([]int, 0) ret = append(ret, s[:index]...) return append(ret, s[index+1:]...) } func main() { all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] removeIndex := RemoveIndex(all, 5) fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] fmt.Println("removeIndex: ", removeIndex) //[0 1 2 3 4 6 7 8 9] removeIndex[0] = 999 fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9] fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9] }

与原始的删除索引解决方案几乎相同,但是我们在返回之前创建了一个新切片来附加。

2024年6月29日 12:07 回复

从切片中删除一个元素(这称为“重新切片”):

shell
package main import ( "fmt" ) func RemoveIndex(s []int, index int) []int { return append(s[:index], s[index+1:]...) } func main() { all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println(all) //[0 1 2 3 4 5 6 7 8 9] all = RemoveIndex(all, 5) fmt.Println(all) //[0 1 2 3 4 6 7 8 9] }
2024年6月29日 12:07 回复

Delete从包中使用slices(从 Go 1.21 开始稳定,在较旧的 Go 版本上您必须导入golang.org/x/exp/slices):

shell
slice := []int{1, 2, 3, 4} slice = slices.Delete(slice, 1, 2) fmt.Println(slice) // [1 3 4]

去游乐场示例

slices.Delete(s, i, j)从 s 中删除元素 s[i:j]

  • 即从索引 i(包括索引 i)到索引 j(不包括索引 j)的元素
  • 或者如果您还记得间隔的数学符号:[i,j)

注意两件事:

  • Delete修改原始切片的内容
  • 需要重新分配slice,否则长度会错误
2024年6月29日 12:07 回复

小一点(代码高尔夫),但在顺序不重要的情况下,您不需要交换值。只需用最后一个位置的副本覆盖要删除的数组位置,然后返回截断的数组。

shell
func remove(s []int, i int) []int { s[i] = s[len(s)-1] return s[:len(s)-1] }

相同的结果。

2024年6月29日 12:07 回复

你的答案