【问题标题】:How to delete an element from a Slice in Golang如何从 Golang 中的切片中删除元素
【发布时间】:2016-09-16 23:26:55
【问题描述】:
fmt.Println("Enter position to delete::")
fmt.Scanln(&pos)

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++
        i++
    } else {
        k++
    }
}

for i := 0; i < (len(arr) - 1); i++ {
    fmt.Println(new_arr[i])
}

我正在使用此命令从 Slice 中删除一个元素,但它不起作用,请建议。

【问题讨论】:

标签: go


【解决方案1】:

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

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

【讨论】:

  • 值得指出的是,这被称为“重新切片”并且相对昂贵,尽管它是 Go 中的惯用方式。只是不要将此与从链表中删除节点之类的操作混淆,因为事实并非如此,如果您要经常这样做,尤其是对于大型集合,您应该考虑避免它的替代设计。
  • 请注意,此方法导致 all 被修改,现在 n 和 all 指向同一底层数组的一部分。这很可能会导致您的代码出现错误。
  • 收到此错误2019/09/28 19:46:25 http: panic serving 192.168.1.3:52817: runtime error: slice bounds out of range [7:5] goroutine 7 [running]:
  • 如果索引是 Slice 中的最后一个元素,这将导致 panic
  • @STEEL 你确定吗? --> play.golang.org/p/IzaGUJI4qeG
【解决方案2】:

除非您关心内容并且可以使用切片追加,否则无需检查每个元素。试试看

pos := 0
arr := []int{1, 2, 3, 4, 5, 6, 7, 9}
fmt.Println("input your position")
fmt.Scanln(&pos)
/* you need to check if negative input as well */
if (pos < len(arr)){
    arr = append(arr[:pos], arr[pos+1:]...)
} else {
    fmt.Println("position invalid")
}

【讨论】:

    【解决方案3】:

    订单很重要

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

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

    但是,这样做效率很低,因为您最终可能会移动所有元素,这很昂贵。

    顺序不重要

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

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

    使用 reslicing 方法,清空一个包含 1 000 000 个元素的数组需要 224 秒,而这个只需要 0.06 ns。

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

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

    【讨论】:

    • 嗯,不是真的。这:s[i] = s[len(s)-1] 肯定会将最后一个元素复制到索引 i 处的元素。然后,return s[:len(s)-1] 返回没有最后一个元素的切片。有两个语句。
    • 失败,len(arr) == 2 并且要删除的元素是最后一个:play.golang.org/p/WwD4PfUUjsM
    • @zenocon 在 Golang 中,数组是 0-index,这意味着长度为 2 的数组的有效索引是 0 和 1。事实上,这个函数不检查数组的边界,并且期望一个要提供的有效索引。当 len(arr) == 2 时,有效参数因此为 01。其他任何事情都会触发越界访问,Go 会恐慌。
    • 添加这个供参考,对于顺序无关选项,最好使用s[len(s)-1], s[i] = 0, s[len(s)-1]。如果您使用的是非原始数组,尤其如此。如果您有指向某些东西的指针,最好在切片之前制作要删除的元素nil,这样您就没有基础数组中的指针。这个answer explains why very well。简而言之:在删除元素的位置移动最后一个元素后,将切片前的最后一个元素归零。
    • 这是一个危险的答案,我猜它已经在程序中引入了数百个错误,这些错误并不完全理解这会修改对切片的所有引用。如果您阅读原始问题,您可以看到想要保留原始切片,这也是不正确的。
    【解决方案4】:

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

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

    同样的结果。

    【讨论】:

    • 最易读的实现是将第一个元素复制到指定的索引s[i] = s[0],然后返回一个仅包含最后 n-1 个元素的数组。 return s[1:]
    • @Kent 解决方案的playground
    • @Kent 与s[1:] 相比s[:len(s)-1] 的问题在于,如果切片稍后获得appended 或删除与appends 混合,则后者的性能会更好。后者保留了切片容量,而前者没有。
    • 如果你用这个函数删除第0个元素,它会反转结果。
    【解决方案5】:

    来自书The Go Programming Language

    从切片中间移除一个元素,保持顺序 其余元素中,使用 copy 滑动编号较高的元素 元素减一以填补空白:

    func remove(slice []int, i int) []int {
      copy(slice[i:], slice[i+1:])
      return slice[:len(slice)-1]
    }
    

    【讨论】:

    • 注意这个方法会导致传入的原始切片被修改。
    【解决方案6】:

    或许你可以试试这个方法:

    // DelEleInSlice delete an element from slice by index
    //  - arr: the reference of slice
    //  - index: the index of element will be deleted
    func DelEleInSlice(arr interface{}, index int) {
        vField := reflect.ValueOf(arr)
        value := vField.Elem()
        if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {
            result := reflect.AppendSlice(value.Slice(0, index), value.Slice(index+1, value.Len()))
            value.Set(result)
        }
    }
    

    用法:

    arrInt := []int{0, 1, 2, 3, 4, 5}
    arrStr := []string{"0", "1", "2", "3", "4", "5"}
    DelEleInSlice(&arrInt, 3)
    DelEleInSlice(&arrStr, 4)
    fmt.Println(arrInt)
    fmt.Println(arrStr)
    

    结果:

    0, 1, 2, 4, 5
    "0", "1", "2", "3", "5"
    

    【讨论】:

    • 可能是因为它不是惯用的并且针对问题所要求的内容进行了过度设计。这是一种有趣的解决方法,但没有人应该使用它。
    • 谢谢!实际上使用接口对我来说效果很好,因为这似乎是通用的,也许
    • 在学习了更多的 Go 之后,我现在有一个关于这个的问题。这不应该不包括 Array 类型吗?不能像在 C 中那样修改数组的大小,所以这可能行不通(至少我没有这样做)。我相信该函数可以减少到 2 行(这也将部分优化它 - 更少的变量声明和更少的操作):var value reflect.Value = reflect.ValueOf(array).Elem(); value.Set(reflect.AppendSlice(value.Slice(0, index), value.Slice(index+1, value.Len()))).
    【解决方案7】:

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

    这里是一些比较用户在这个线程和原始帖子中的答案的代码。这里有一个go playground 来乱用这段代码。

    基于附加的移除

    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 对此进行了更详细的介绍。

    下面的例子我不会进入,但它为我们的目的做了同样的事情。并且只是说明使用副本没有什么不同。

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

    问题原始答案

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

    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 的变化,删除索引并分配它的操作也不会引起变化!太棒了!

    这段代码有点长,上面可以改成这个。

    正确答案

    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 9 9]
        fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9]
    }
    

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

    【讨论】:

    • 这应该是问题的答案,因为它是唯一解释修改切片支持数组的风险的问题
    【解决方案8】:

    这里是带有指针的游乐场示例。 https://play.golang.org/p/uNpTKeCt0sH

    package main
    
    import (
        "fmt"
    )
    
    type t struct {
        a int
        b string
    }
    
    func (tt *t) String() string{
        return fmt.Sprintf("[%d %s]", tt.a, tt.b)
    }
    
    func remove(slice []*t, i int) []*t {
      copy(slice[i:], slice[i+1:])
      return slice[:len(slice)-1]
    }
    
    func main() {
        a := []*t{&t{1, "a"}, &t{2, "b"}, &t{3, "c"}, &t{4, "d"}, &t{5, "e"}, &t{6, "f"}}
        k := a[3]
        a = remove(a, 3)
        fmt.Printf("%v  ||  %v", a, k)
    }
    

    【讨论】:

      【解决方案9】:

      我采用以下方法来删除切片中的项目。这有助于其他人的可读性。而且也是不可变的。

      func remove(items []string, item string) []string {
          newitems := []string{}
      
          for _, i := range items {
              if i != item {
                  newitems = append(newitems, i)
              }
          }
      
          return newitems
      }
      

      【讨论】:

      • 我更喜欢这种方法,您实际上是在删除所有出现的项目。
      • 这可以很容易地转换为从切片中过滤几个项目,很好!
      【解决方案10】:

      也许这段代码会有所帮助。

      它删除具有给定索引的项目。

      获取数组和要删除的索引并返回一个新数组,非常类似于 append 函数。

      func deleteItem(arr []int, index int) []int{
        if index < 0 || index >= len(arr){
          return []int{-1}
        }
      
          for i := index; i < len(arr) -1; i++{
            arr[i] = arr[i + 1]
      
          }
      
          return arr[:len(arr)-1]
      }
      

      你可以在这里玩代码:https://play.golang.org/p/aX1Qj40uTVs

      【讨论】:

        【解决方案11】:

        这就是您删除切片惯用方式的方式。您不需要构建一个内置在追加中的函数。 在这里试试https://play.golang.org/p/QMXn9-6gU5P

        z := []int{9, 8, 7, 6, 5, 3, 2, 1, 0}
        fmt.Println(z)  //will print Answer [9 8 7 6 5 3 2 1 0]
        
        z = append(z[:2], z[4:]...)
        fmt.Println(z)   //will print Answer [9 8 5 3 2 1 0]
        

        【讨论】:

          【解决方案12】:

          最好的方法是使用 append 函数:

          package main
          
          import (
              "fmt"
          )
          
          func main() {
              x := []int{4, 5, 6, 7, 88}
              fmt.Println(x)
              x = append(x[:2], x[4:]...)//deletes 6 and 7
              fmt.Println(x)
          }
          

          https://play.golang.org/p/-EEFCsqse4u

          【讨论】:

            【解决方案13】:

            在不搬家的情况下找到方法here

            • 更改顺序
            a := []string{"A", "B", "C", "D", "E"}
            i := 2
            
            // Remove the element at index i from a.
            a[i] = a[len(a)-1] // Copy last element to index i.
            a[len(a)-1] = ""   // Erase last element (write zero value).
            a = a[:len(a)-1]   // Truncate slice.
            
            fmt.Println(a) // [A B E D]
            
            • 保持秩序
            a := []string{"A", "B", "C", "D", "E"}
            i := 2
            
            // Remove the element at index i from a.
            copy(a[i:], a[i+1:]) // Shift a[i+1:] left one index.
            a[len(a)-1] = ""     // Erase last element (write zero value).
            a = a[:len(a)-1]     // Truncate slice.
            
            fmt.Println(a) // [A B D E]
            

            【讨论】:

              【解决方案14】:

              从切片中间移除一个元素,保持顺序 其余元素中,使用 copy 滑动编号较高的元素 元素减一以填补空白:

              func remove(slice []int, i int) []int {
                copy(slice[i:], slice[i+1:])
                return slice[:len(slice)-1]
              }
              

              如果不需要保留顺序,我们可以简单地将最后一个元素移动到间隙。

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

              【讨论】:

                【解决方案15】:

                由于 Slice 是由数组支持的,并且由于您无法从数组中删除元素而不重新洗牌内存;而且我不想做那个丑陋的代码;这是一个伪代码,用于保留已删除项目的索引;基本上我想要一个有序的切片,即使在删除之后位置也很重要

                type ListSlice struct {
                  sortedArray []int
                  deletedIndex map[int]bool
                }
                func lenSlice(m ListSlice)int{
                    return len(m.sortedArray)
                }
                func deleteSliceElem(index int,m ListSlice){
                    m.deletedIndex[index]=true
                }
                func getSliceElem(m ListSlice,i int)(int,bool){
                    _,deleted :=m.deletedIndex[i]
                    return m.sortedArray[i],deleted
                }
                
                for i := 0; i < lenSlice(sortedArray); i++ {
                        
                        k,deleted := getSliceElem(sortedArray,i)
                        if deleted {continue}
                        ....
                        deleteSliceElem(i,sortedArray)
                
                }
                
                m := ListSlice{sortedArray: []int{5, 4, 3},deletedIndex: make(map[int]bool) }
                ...
                

                【讨论】:

                  【解决方案16】:

                  T. Claverie 目前投票最多的答案是正确的,但我发现如果仅在需要时才执行交换(即除切片的最后一个元素之外的所有元素),则该算法更清晰。这可以通过一个简单的 if 守卫来实现。

                  顺序不重要/不进行边界检查

                  func remove(s []int, i int) []int {
                      // bring element to remove at the end if its not there yet
                      if i != len(s)-1 {
                          s[i] = s[len(s)-1]
                      }
                   
                      // drop the last element
                      return s[:len(s)-1]
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    language tutorial 中我们了解到:

                    切片就像对数组的引用。 切片不存储任何数据,它只描述底层数组的一部分。 更改切片的元素会修改其底层数组的相应元素。

                    因此,在切片上使用append 函数不关心我们正在处理的值的来源和目的地,对于 Go 哲学来说是非常危险和错误的。

                    因此,正确的解决方案是使用引用新数组而不是“主”数组的切片。 这可以通过make 构造创建一个新切片来实现。

                    func removeAt(slice []int, index int) []int {
                        newSlice := make([]int, 0) //Create a new slice of type []int and length 0
                        newSlice = append(newSlice, slice[:index]...) //Copies the values contained in the old slice to the new slice up to the index (excluded)
                        if index != len(slice)-1 {
                            newSlice = append(newSlice, slice[index+1:]...) //If the index to be removed was different from the last one, then proceed to copy the following values of the index to the end of the old slice
                        }
                        return newSlice
                    }
                    

                    通过这种方式,我们能够安全地删除切片的元素,无论我们将在函数返回时使用什么。


                    由于我使用了一个函数来回答这个问题,所以最好按如下方式处理任何错误:

                    func removeAt(slice []int, index int) ([]int, error) {
                        if index < 0 {
                            return nil, fmt.Errorf("index (%d) cannot be a negative number", index)
                        }
                        if index >= len(slice) {
                            return nil, fmt.Errorf("index (%d) cannot be a number greater or equal than the length of slice (%d)", index, len(slice))
                        }
                    
                        newSlice := make([]int, 0)
                        newSlice = append(newSlice, slice[:index]...)
                        if index != len(slice)-1 {
                            newSlice = append(newSlice, slice[index+1:]...)
                        }
                        
                        return newSlice, nil
                    }
                    

                    或者更好的是,实现可以通过接口处理多种类型的功能。 但是,所有这些都是一个很好的做法,因为您构建了一个函数来执行此操作,这与提出的问题无关。

                    不过,可以在 here 找到 Go 操场上的测试示例。

                    【讨论】:

                      猜你喜欢
                      • 2016-03-10
                      • 2018-06-15
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-10-17
                      • 2019-02-05
                      • 2017-12-06
                      相关资源
                      最近更新 更多