1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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中删除元素,但是它不起作用,请提出建议。

  • 这是一本好书:blog.golang.org/go-slices-usage-and-internals
  • 也是这样:sitepoint.com/arrays-slices-basic-oop-go

订单事项

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

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

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

顺序并不重要

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

1
2
3
4
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

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

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

1
2
3
4
5
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),依此类推。

  • 看起来,如果您不关心保留原始切片,甚至不需要交换,并且s[i] = s[len(s)-1]; return s[:len(s)-1]就足够了。
  • @bgp这只是弹出(删除最后一个元素),而不是删除原始问题中提供的下索引。同样,您可以使用return s[1:]进行移位(删除第一个元素),这也不能回答原始问题。
  • 嗯,不是真的。这:s[i] = s[len(s)-1]肯定会将最后一个元素复制到索引为i的元素。然后,return s[:len(s)-1]返回没有最后一个元素的切片。有两个陈述。
  • len(arr)== 2失败,要删除的elem是最后一个:play.golang.org/p/WwD4PfUUjsM
  • @zenocon在Golang中,数组的索引为0,这意味着长度为2的数组的有效索引为0和1。实际上,此函数不检查数组的边界,而是期望提供有效的索引。当len(arr)== 2时,有效参数因此为0或1。其他任何情况都将触发越界访问,而Go将出现恐慌。
  • @ T.Claverie哦,我知道。我只是指出,提出的算法存在缺陷。
  • 我认为,如果您要迭代删除几乎所有元素,例如在就地过滤器中,"订单重要"版本的运行时间为O(n ^ 2),而"订单无关紧要+合并排序"取O(n + n log n)= O(n log n)。

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

1
2
3
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解决方案的游乐场
  • @Kent做s[1:]s[:len(s)-1]的问题在于,如果切片后来得到append或删除与append混合在一起,则后者的性能会更好。后者将切片容量保留在哪里,而前者则没有。
  • 如果使用此功能删除第0个元素,它将反转结果。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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中惯用的方式。只是不要将其与诸如从链表中删除节点之类的操作混淆,因为这样做不是那么容易的,如果您打算做很多事情,尤其是对于大型集合,则应考虑选择避免这种情况的替代设计。
  • 是的,在Golang中这是惯用的方式,即使在C / Assembly中从排序数组中删除一个随机元素也是很昂贵的,您需要将所有右元素向左移动(复制)一个位置。是的,在某些用例中,链接列表是从列表中删除随机元素的更好解决方案。
  • 注意,此方法将导致全部被修改,现在n和全部都指向同一基础数组的一部分。这很可能导致代码中的错误。
  • 收到此错误20190928 19:46:25 http: panic serving 192.168.1.3:52817: runtime error: slice bounds out of range [7:5] goroutine 7 [running]:

摘自《 Go编程语言》一书

To remove an element from the middle of a slice, preserving the order
of the remaining elements, use copy to slide the higher-numbered
elements down by one to fill the gap:

1
2
3
4
func remove(slice []int, i int) []int {
  copy(slice[i:], slice[i+1:])
  return slice[:len(slice)-1]
}
  • 请注意,此方法会导致传入的原始切片被修改。

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

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

基于附加的删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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也被修改了。关于这一点的有效细节。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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]
}

问题原答案

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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发生更改,删除索引并为其分配索引的操作也不会导致更改!太棒了!

这段代码有点冗长,因此上面的代码可以更改为此代码。

正确答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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]
}

几乎与原始的remove index解决方案相同,但是我们在返回之前制作了一个要附加到其上的新切片。


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

1
2
3
4
5
6
7
8
9
10
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")
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
}

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

1
2
3
4
5
6
7
8
9
10
11
// 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)
    }
}

用法:

1
2
3
4
5
6
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)

结果:

1
2
0, 1, 2, 4, 5
"0","1","2","3","5"
  • 可能是因为它不是针对问题的惯用语言和过度设计的。解决它是一种有趣的方法,但是没有人应该使用它。