ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Golang 中的切片中删除元素

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

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

这个问题在 Go 1.18 和下面的泛型中有答案:1 2

J
Jon Egerton

订单事项

如果要保持数组有序,则必须将删除索引右侧的所有元素向左移动一位。希望这可以在 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。

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

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


嗯,真的不是。这: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 时,有效参数因此为 0 或 1。其他任何事情都会触发越界访问,Go 会恐慌。
添加此作为参考,对于 顺序无关 选项,最好使用 s[len(s)-1], s[i] = 0, s[len(s)-1]。如果您使用的是非原始数组,尤其如此。如果您有指向某些东西的指针,最好在切片之前制作要删除的元素 nil,这样您就没有基础数组中的指针。这个answer explains why very well。简而言之:在删除元素的位置移动最后一个元素后,将切片前的最后一个元素归零。
这是一个危险的答案,我猜已经在程序中引入了数百个错误,这些错误并不完全理解这会修改对切片的所有引用。如果您通读原始问题,您可以看到想要保留原始切片,这也是不正确的。
e
eatingthenight

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

这是一些比较用户在此线程和原始帖子中的答案的代码。这里有一个 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]
}

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


这应该是问题的答案,因为它是唯一解释修改切片支持数组的风险的问题
append 如果不需要,它不会创建新的支持数组,那么为什么要删除创建一个新切片呢? go.dev/play/p/8V9rbjrDkt5
如果您阅读了我的完整答案,那么您所问的问题就会得到明确的回答。
append 将在有空间时修改后备数组。从切片中删除是创建空间,因此任何涉及在删除时创建新切片的解决方案显然是错误的。
w
wasmup

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

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 中,从排序数组中删除一个随机元素也很昂贵,您需要将所有正确的元素向左移动(复制)一个位置。是的,在某些用例中,链接列表是从列表中删除随机元素的更好解决方案。
请注意,此方法导致 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]:
@STEEL你确定吗? --> play.golang.org/p/IzaGUJI4qeG
D
David

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

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:]
playground @Kent 的解决方案
@Kent 与 s[1:] 相比 s[:len(s)-1] 的问题在于,如果稍后切片 append 或删除与 append 混合,则后者的性能会更好。后者保留了切片容量,而前者没有。
如果使用此函数删除第 0 个元素,则会反转结果。
Y
Yagiz Degirmenci

这就是您删除 切片 惯用方式的方式。您不需要构建一个内置在追加中的函数。在这里试试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]

O
Oleksandr Mosur

来自书The Go Programming Language

要从切片中间删除一个元素,保留剩余元素的顺序,请使用 copy 将编号较高的元素向下滑动 1 以填充间隙: func remove(slice []int, i int) []int { 复制(slice[i:], slice[i+1:]) 返回 slice[:len(slice)-1] }


请注意,此方法会导致传入的原始切片被修改。
@eatingthenight append 可以修改后备数组,为什么不能删除? go.dev/play/p/9eQStgiqYYK
@blackbox 不对, append 不会修改后备数组。您的代码将 append 的值分配给后备数组 s2 = append(s2, 2),如果您省略此步骤,后备数组 s2 将保持不变。与 go.dev/play/p/7EubNjwOFuD 比较
A
Alex80286

使用 slices 包中的 Delete(Go 1.18 的实验性版本,计划在 Go 1.19 中稳定):

slice := []int{1, 2, 3, 4}
slice = slices.Delete(slice, 1, 2)
fmt.Println(slice) // [1 3 4]

Go playground example

slices.Delete(s, i, j) 从 s 中删除元素 s[i:j]。注意两点:

删除修改原始切片的内容

不过,您需要重新分配切片,否则它的长度会错误


它是这个 append(slice[:s], slice[s+1:]...) 的包装函数。更多信息:cs.opensource.google/go/x/exp/+/0b5c67f0:slices/slices.go;l=156
M
Madhan Ganesh

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

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

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

    return newitems
}

我更喜欢这种方法,您实际上是在删除所有出现的项目。
这可以很容易地转换为从切片中过滤多个项目,很好!
Y
Yagiz Degirmenci

最好的方法是使用 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


A
Alberto Benegiamo

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

F
Franci

找到一种方式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]

K
Karl Doenitz

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

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

可能是因为它不是惯用的并且针对问题所提出的问题进行了过度设计。这是一种有趣的解决方法,但没有人应该使用它。
谢谢!实际上使用接口对我来说工作得很好,因为这似乎是通用的,也许
在学习了更多的围棋之后,我现在有一个关于这个的问题。这不应该不包括 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())))
K
Kroksys

使用泛型,您可以传递 any 类型的切片。

// Removes slice element at index(s) and returns new slice
func remove[T any](slice []T, s int) []T {
    return append(slice[:s], slice[s+1:]...)
}

用法

slice := []int{1, 2, 3, 4}
result := remove(slice, 0)
fmt.Println(result)
// [2 3 4]

示例
https://go.dev/play/p/LhPGvEuZbRA


M
MartenCatcher

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

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

获取数组和要删除的索引并返回一个新数组,非常类似于 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


Y
Yuri Giovani

要从切片中间删除一个元素,保留剩余元素的顺序,请使用 copy 将编号较高的元素向下滑动 1 以填充间隙: func remove(slice []int, i int) []int { 复制(slice[i:], slice[i+1:]) 返回 slice[:len(slice)-1] }

如果不需要保留顺序,我们可以简单地将最后一个元素移动到间隙。 func remove(slice []int, i int) []int { slice[i] = slice[len(slice)-1] return slice[:len(slice)-1] }


A
Andrea Morandi

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 操场上的测试示例。


G
GoGo

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

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

z
zangw

GO wiki slice tricks

删除

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

删除而不保留顺序

a[i] = a[len(a)-1] 
a = a[:len(a)-1]

注意 如果元素的类型是指针或带有指针字段的结构体,需要进行垃圾回收,上述 Cut 和 Delete 的实现存在潜在的内存泄漏问题:一些有值的元素仍然被切片 a 引用,因此无法收集。下面的代码可以解决这个问题:

删除

copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]

删除而不保留顺序

a[i] = a[len(a)-1]
a[len(a)-1] = nil
a = a[:len(a)-1]

H
Highdeger

你需要稍微改变你的代码,

new_arr := make([]int, (len(arr) - 1))
for i := 0; i < len(arr); i++ {
    if i != pos {
        new_arr = append(new_arr, arr[i])
    }
}

为了更有效的循环,您可以使用它

for i, item := range arr {
    ...
}

最后,您可以使用本机切片功能来做到这一点

new_arr = append(arr[:2], arr[3:])

最后一个解决方案删除索引 2 中的元素并将新切片放入 new_arr。


u
user1858478

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

A
Alex Punnen

由于 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) }
...