如何从Golang中的Slice中删除元素


109
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中删除元素,但是它不起作用,请提出建议。



Answers:


176

订单事项

如果要使数组保持有序,则必须将删除索引右侧的所有元素向左移动一。希望可以在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[len(s)-1], s[i] = s[i], s[len(s)-1]
    return s[:len(s)-1]
}

使用重新分片方法,清空1000000个元素的数组需要224s,而此操作仅需要0.06ns。我怀疑在内部,go仅更改了切片的长度,而没有对其进行修改。

编辑1

基于以下注释的快速注释(感谢!)。

因为目的是删除元素,所以当顺序无关紧要时,只需进行一次交换,第二次将被浪费:

func remove(s []int, i int) []int {
    s[i] = s[len(s)-1]
    // We do not need to put s[i] at the end, as it will be discarded anyway
    return s[:len(s)-1]
}

另外,此答案不执行边界检查。它期望一个有效的索引作为输入。这意味着大于或等于len的负值或索引将导致Go出现恐慌。切片和数组的索引为0,删除数组的第n个元素意味着将提供输入n-1。要删除第一个元素,请调用remove(s,0),要删除第二个元素,请调用remove(s,1),依此类推。


20
看起来,如果您不关心保留原始切片,甚至不需要交换s[i] = s[len(s)-1]; return s[:len(s)-1]就足够了。
布拉德·皮博迪

@bgp这只是弹出(删除最后一个元素),而不是删除原始问题中提供的下索引。同样,您可以移动(删除第一个元素)return s[1:],也不能回答原始问题。
shadyyx16年

2
嗯,不是真的。这:s[i] = s[len(s)-1]绝对将最后一个元素复制到index处的元素i。然后,return s[:len(s)-1]返回没有最后一个元素的切片。有两个陈述。
布拉德·皮博迪

1
len(arr)== 2失败,要删除的elem是最后一个:play.golang.org/p/WwD4PfUUjsM
zenocon,

1
@zenocon在Golang中,数组的索引为0,这意味着长度为2的数组的有效索引为0和1。实际上,此函数不会检查数组的边界,而是期望提供有效的索引。当len(arr)== 2时,有效参数因此为01。其他任何事情都会触发访问权限,Go会感到恐慌。
T. Claverie '18年

40

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

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

8
值得指出的是,这被称为“重新切片”,并且相对昂贵,尽管这是Go语言中惯用的方式。只是不要将其与诸如从链接列表中删除节点之类的操作混淆,因为不是那样,并且如果您打算做很多事情(尤其是对于大型集合),则应考虑采用其他避免这种情况的设计。
evanmcdonnal,2016年

是的,这在Golang中是惯用的方式,即使在C / Assembly中从已排序的数组中删除一个随机元素也很昂贵,您需要将所有右元素向左移动(复制)一个位置。是的,在某些用例中,链接列表是从列表中删除随机元素的更好解决方案。

1
请注意,此方法导致全部被修改,现在n和全部都指向同一基础数组的一部分。这很可能导致代码中的错误。
mschuett

1
得到这个错误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]:
OhhhThatVarun

10
panic如果索引是切片中的最后一个元素,这将导致
STEEL

28

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

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

结果相同。


13
最易读的实现是将第一个元素复制到指定的索引s[i] = s[0],然后返回仅包含最后n-1个元素的数组。return s[1:]
肯特,

4
@Kent解决方案的游乐场
stevenspiel

2
@Kent做s[1:]vs的问题s[:len(s)-1]是,如果稍后获取sappend或将删除与appends混合,则后者的性能会更好。后者将切片容量保留在哪里,而前者则没有。
戴夫C,

1
如果使用此功能删除第0个元素,它将反转结果。
ivarec

21

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

这是一些代码,用于比较用户在该线程中的答案和原始帖子。这是一个去操场这个代码的。

基于附加的删除

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被修改了。关于这一点的有效细节。

下面的示例我不会涉及,但出于我们的目的,它会做同样的事情。只是说明使用复制没有什么不同。

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

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


3
这应该是该问题的答案,因为它是唯一一个说明修改片的后备阵列的风险的问题
JessG

在基于附加的删除中,第一个分片all也将更改,removeIndex := RemoveIndex(all, 5)因为如果附加存储具有足够的容量,附加将重用同一基础数组(删除元素当然不需要更多的容量)。相反,如果我们添加元素并分配给removeIndex,则append将分配一个新数组,并且all不会更改。
blackgreen

11

这是你如何删除golang方式。您不需要构建附加在附件中的函数。在这里尝试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]

8

摘自《 Go编程语言》一书

要从切片中间删除元素,并保留其余元素的顺序,请使用copy将编号较高的元素向下滑动一个以填补空白:

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

3
请注意,此方法将导致传入的原始切片被修改。
mschuett

7

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

func remove(items []string, item string) []string {
    newitems := []string{}

    for _, i := range items {
        if i != item {
            newitems = append(newitems, i)
        }
    }

    return newitems
}

我实际上更喜欢这种方法,因为实际上您正在删除所有出现的项目。
eexit

可以很容易地将其转换为从切片中过滤多个项目,很好!
eldad征费

1

也许您可以尝试以下方法:

// 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"

1
可能是因为它不是针对问题的惯用语言和过度设计的。这是解决它的一种有趣方式,但是没有人应该使用它。
mschuett

1
谢谢!实际上,使用接口对我来说效果很好,因为这似乎是通用的,也许
DADi590 '19

1

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

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

接受数组和要删除的索引,然后返回一个新数组,就像添加函数一样。

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



1

在这里找到一种无需重新定位的方法。

  • 变更单
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]

0

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

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")
}

-1

这是带有指针的操场示例。 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)
}
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.