删除切片中的元素


139
func main() {
    a := []string{"Hello1", "Hello2", "Hello3"}
    fmt.Println(a)
    // [Hello1 Hello2 Hello3]
    a = append(a[:0], a[1:]...)
    fmt.Println(a)
    // [Hello2 Hello3]
}

这个带有添加功能的删除技巧如何工作?

似乎它正在抓取第一个元素(空数组)之前的所有内容

然后将所有内容附加在第一个元素之后(位置零)

...(点点)是做什么的?



3
为什么不看一下语言规范? ...有详细解释吗?
Volker

9
一方面,绝对正确(golang.org/ref/spec,每个人);另一方面,对于迁移Pythonista等的这些习语有足够的不熟悉,因此我不介意为其他人找到解释。
twotwotwo

Answers:


276

a切片在哪里,i是要删除的元素的索引:

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

... 是Go中可变参数的语法。

基本上,在定义函数时,它将所有传递的参数放入该类型的一个切片中。这样,您可以传递任意数量的参数(例如,可以传递fmt.Println任意数量的参数)。

现在,当调用一个函数时,...做相反的事情:它将一个切片解压缩并将其作为单独的参数传递给可变参数函数。

所以这行的作用是:

a = append(a[:0], a[1:]...)

本质上是:

a = append(a[:0], a[1], a[2])

现在,您可能想知道,为什么不做

a = append(a[1:]...)

好吧,函数的定义append

func append(slice []Type, elems ...Type) []Type

因此,第一个参数必须是正确类型的切片,第二个参数是可变参数,因此我们传入一个空切片,然后解压切片的其余部分以填充参数。


35
如果我是切片中的最后一个元素,您不会超出范围例外吗?a = append(a[:i], a[i+1:]...)
themihai

5
@DaveC在我的项目中使用切片时,我确实得到了该错误:/
Tyguy7 '16

3
来自规范的@ Tyguy7:“对于数组或字符串,如果0 <=低<=高<= len(a),则索引在范围内,否则它们超出范围。” 在您的情况下,可能高<低;在这种情况下,您会得到错误。(golang.org/ref/spec#Slice_expressions
MLG

2
这个表现如何?我非常希望它不会在引擎盖下创建一个全新的片段
。.– joonas.fi

7
@ Tyguy7我认为您尝试在循环中删除slice的元素。因此,您必须小心使用索引。
Nikolay Bystritskiy

42

有两种选择:

答:您关心保留数组顺序:

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

B:您不在乎保留订单(这可能更快):

a[i] = a[len(a)-1] // Replace it with the last one. CAREFUL only works if you have enough elements.
a = a[:len(a)-1]   // Chop off the last one.

如果您的数组是指针,请参见链接以了解内存泄漏的含义。

https://github.com/golang/go/wiki/SliceTricks


这很有趣,但并未真正回答问题
Bryan

很好,但是如果可以删除数组中的唯一元素,那就更好了
Naguib Ihab

2
抬起头来,尝试使用b中的第一个元素(替换为最后一个元素)显然无法正常工作,如果您尝试删除切片中的最后一个元素,大声笑
Sirens

13

不要将[a:]-,[:b]-和- [a:b]标记中的索引视为元素索引,而应将它们视为元素周围和元素之间的间隙的索引,从0在元素索引为之前的间隙索引开始0

在此处输入图片说明

仅查看蓝色数字,就更容易了解发生了什么:将[0:3]所有内容围起来,[3:3]为空并且[1:2]会产生{"B"}。然后[a:]是的短版[a:len(arrayOrSlice)][:b]的短版[0:b][:]的短版[0:len(arrayOrSlice)]。后者通常用于在需要时将数组变成切片。


1
这有助于解释为什么答案是themihai 对dave 答案评论 “否”,即使提到第ith个元素,也提到[i + 1:]:play.golang.org/p/E0lQ3jPcjX5
Nick P

5

...是可变参数的语法。

我认为它是由编译器使用slice(来实现的[]Type),就像函数append一样:

func append(slice []Type, elems ...Type) []Type

当您在“附加”中使用“元素”时,实际上它是slice([] type)。所以“ a = append(a[:0], a[1:]...)”的意思是“ a = append(a[0:0], a[1:])

a[0:0] 是没有任何东西的切片

a[1:] 是“ Hello2 Hello3”

这是这样的


2
a[0:0]不是nil一个长度为0的切片。a[0:0]唯一的nil,如果anil
icza 2015年

5

我得到了可接受的答案解决方案的索引超出范围错误。原因:范围开始时,不是一个一个地迭代值,而是一个索引迭代。如果您在范围内修改切片,则会引起一些问题。

旧答案:

chars := []string{"a", "a", "b"}

for i, v := range chars {
    fmt.Printf("%+v, %d, %s\n", chars, i, v)
    if v == "a" {
        chars = append(chars[:i], chars[i+1:]...)
    }
}
fmt.Printf("%+v", chars)

预期:

[a a b], 0, a
[a b], 0, a
[b], 0, b
Result: [b]

实际:

// Autual
[a a b], 0, a
[a b], 1, b
[a b], 2, b
Result: [a b]

正确方法(解决方案):

chars := []string{"a", "a", "b"}

for i := 0; i < len(chars); i++ {
    if chars[i] == "a" {
        chars = append(chars[:i], chars[i+1:]...)
        i-- // form the remove item index to start iterate next item
    }
}

fmt.Printf("%+v", chars)

资料来源:https : //dinolai.com/notes/golang/golang-delete-slice-item-in-range-problem.html


3

在golang的Wiki中,它显示了一些切片技巧,包括从slice中删除元素。

链接:在此处输入链接说明

例如,a是您要删除数字i元素的切片。

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

要么

a = a[:i+copy(a[i:], a[i+1:])]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.