删除for中的slice元素 [英] Remove slice element within a for

查看:124
本文介绍了删除for中的slice元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从片段 a 中删除​​元素 i 的习惯方法似乎是保留顺序:



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



我想知道哪种方法可以在循环内完成。据我了解,它是不可能在范围内使用它:

  for i:= range a {// BAD 
if conditionMeets(a [i]){
a = append(a [:i],a [i + 1:] ...)
}
}

但是可以使用 len(a)

  for i:= 0; 我< LEN(一); i ++ {
if conditionMeets(a [i]){
a = append(a [:i],a [i + 1:] ...)
}
}

有没有比使用 len 或追加

解决方案

您提出的解决方案不正确。问题是,当你从一个切片中移除一个元素时,所有后续的元素都被移动。但是循环并不知道你改变了底层切片和循环变量(索引)像往常一样增加,即使在这种情况下,它不应该因为你跳过了一个元素。



如果切片包含两个彼此相邻的元素,两个元素都需要移除,第二个元素将不会被检查并且不会被移除。



所以如果你删除一个元素,循环变量必须手动递减!让我们看一个例子:删除以a开头的单词:

  func conditionMeets(s string)bool {
返回strings.HasPrefix(s,a)
}

解决方案(在 Go Playground

a>):

  a:= [] string {abc,bbc,aaa,aoi ,ccc} 
for i:= 0;我< LEN(一); i ++ {
if conditionMeets(a [i]){
a = append(a [:i],a [i + 1:] ...)
i--
}

fmt.Println(a)

输出:

  [bbc ccc] 

或者更好:使用向下循环,因此不需要手动增加变量,因为在这种情况下,移位元素位于切片的已处理部分。

  a:= [] string {abc,bbc,aaa,aoi,ccc } 
for i:= len(a) - 1; i> = 0; i-- {
if conditionMeets(a [i]){
a = append(a [:i],a [i + 1:] ...)
}
}
fmt.Println(a)

输出是相同的。



替代许多删除



如果您必须删除许多元素,这可能会很慢,因为您必须做很多的拷贝( append()拷贝)。想象一下:你有一个有1000个元素的切片;只是删除第一个元素需要将999个元素复制到前面。还会创建许多新的切片描述符:每个元素的移除都会创建 2 新切片描述符( a [:i] 一个[i + 1:] 加上 a 必须被更新(追加())。在这种情况下,将不可移动元素复制到新切片可能更有效。



一个有效的解决方案:

  a:= [] string {abc,bbc,aaa,aoi,ccc} 
b:= make([]字符串,len(a))
复制:= 0
为_,s:= range(a){
if!conditionMeets(s){
b [copied] = s
copied ++
}
}
b = b [:copied]
fmt.Println(b)

该解决方案分配一个与源相同长度的片,因此不会执行新的分配(和复制)。该解决方案还可以使用范围循环。如果你想在 a 结果中,将结果赋给 a a = b [:复制]



输出结果是一样的。

就地我们也可以通过保持2个指数并分配(向前复制)非循环来实现就地的循环移除 -

要记住的一件事是,我们应该将被删除元素的位置归零,以便移除不可到达值的引用,以便GC可以做它的工作。这也适用于其他解决方案,但在此仅提及。



示例实现:

  a:= [] string {abc,bbc,aaa,aoi,ccc} 
复制:= 0
for i:= 0;我< LEN(一); i ++ {
if!conditionMeets(a [i]){
a [copied] = a [i]
复制++
}
}
for i: =复制;我< LEN(一); i ++ {
a [i] =//删除元素的零位(允许gc完成它的工作)
}
a = a [:copied]
fmt.Println a)

输出是一样的。尝试去游乐场的所有例子。


An idiomatic method to remove an element i from a slice a, preserving the order, seems to be:

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

I was wondering which would be the best way to do it inside a loop. As I understand, it is not possible to use it inside a range for:

for i := range a { // BAD
    if conditionMeets(a[i]) {
        a = append(a[:i], a[i+1:]...)
    }
}

However it is possible to use len(a). [EDIT: this doesn't work, see answers below]

for i := 0; i < len(a); i++ {
    if conditionMeets(a[i]) {
        a = append(a[:i], a[i+1:]...)
    }
}

Is there a better or more idiomatic way than using len or append?

解决方案

Your proposed solution is incorrect. The problem is that when you remove an element from a slice, all subsequent elements are shifted. But the loop doesn't know that you changed the underlying slice and loop variable (the index) gets incremented as usual, even though in this case it shouldn't because then you skip an element.

And if the slice contains 2 elements which are right next to each other both of which need to be removed, the second one will not be checked and will not be removed.

So if you remove an element, the loop variable has to be decremented manually! Let's see an example: remove words that start with "a":

func conditionMeets(s string) bool {
    return strings.HasPrefix(s, "a")
}

Solution (try it with all other examples below on the Go Playground):

a := []string{"abc", "bbc", "aaa", "aoi", "ccc"}
for i := 0; i < len(a); i++ {
    if conditionMeets(a[i]) {
        a = append(a[:i], a[i+1:]...)
        i--
    }
}
fmt.Println(a)

Output:

[bbc ccc]

Or better: use a downward loop and so you don't need to manually increment the variable, because in this case the shifted elements are in the "already processed" part of the slice.

a := []string{"abc", "bbc", "aaa", "aoi", "ccc"}
for i := len(a) - 1; i >= 0; i-- {
    if conditionMeets(a[i]) {
        a = append(a[:i], a[i+1:]...)
    }
}
fmt.Println(a)

Output is the same.

Alternate for many removals

If you have to remove "many" elements, this might be slow as you have to do a lot of copy (append() does the copy). Imagine this: you have a slice with 1000 elements; just removing the first element requires copying 999 elements to the front. Also many new slice descriptors will be created: every element removal creates 2 new slice descriptors (a[:i], a[i+1:]) plus a has to be updated (the result of append()). In this case it might be more efficient to copy the non-removable elements to a new slice.

An efficient solution:

a := []string{"abc", "bbc", "aaa", "aoi", "ccc"}
b := make([]string, len(a))
copied := 0
for _, s := range(a) {
    if !conditionMeets(s) {
        b[copied] = s
        copied++
    }
}
b = b[:copied]
fmt.Println(b)

This solution allocates a slice with the same length as the source, so no new allocations (and copying) will be performed. This solution can also use the range loop. And if you want the result in a, assign the result to a: a = b[:copied].

Output is the same.

In-place alternate for many removals (and for general purposes)

We can also do the removal "in place" with a cycle, by maintaining 2 indices and assigning (copying forward) non-removable elements in the same slice.

One thing to keep in mind is that we should zero places of removed elements in order to remove references of unreachable values so the GC can do its work. This applies to other solutions as well, but only mentioned here.

Example implementation:

a := []string{"abc", "bbc", "aaa", "aoi", "ccc"}
copied := 0
for i := 0; i < len(a); i++ {
    if !conditionMeets(a[i]) {
        a[copied] = a[i]
        copied++
    }
}
for i := copied; i < len(a); i++ {
    a[i] = "" // Zero places of removed elements (allow gc to do its job)
}
a = a[:copied]
fmt.Println(a)

Output is the same. Try all the examples on the Go Playground.

这篇关于删除for中的slice元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆