提问人:Anchal Sarraf 提问时间:5/20/2016 最后编辑:CarsonAnchal Sarraf 更新时间:10/18/2023 访问量:616616
如何从 Golang 中的切片中删除元素
How to delete an element from a Slice in 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])
}
我正在使用此命令从 Slice 中删除元素,但它不起作用,请建议。
答:
从 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]
}
评论
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]:
除非您关心内容,否则无需检查每个元素,并且可以使用切片附加。尝试一下
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")
}
订单很重要
如果要保持数组的顺序,则必须将删除索引右侧的所有元素向左移动一个。希望这可以在 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]
}
使用切片方法,清空 1 000 000 个元素的数组需要 224 秒,而使用这种方法只需要 0.06ns。
此答案不执行边界检查。它需要一个有效的索引作为输入。这意味着大于或等于初始值的负值或指数将导致 Go 恐慌。len(s)
切片和数组被索引为 0,删除数组的第 n 个元素意味着提供输入 n-1。若要删除第一个元素,请调用 remove(s, 0),若要删除第二个元素,请调用 remove(s, 1),依此类推。
评论
s[i] = s[len(s)-1]
i
return s[:len(s)-1]
s[len(s)-1], s[i] = 0, s[len(s)-1]
nil
次要的一点(代码高尔夫),但在顺序无关紧要的情况下,您不需要交换值。只需用最后一个位置的副本覆盖要删除的数组位置,然后返回一个截断的数组。
func remove(s []int, i int) []int {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}
同样的结果。
评论
s[i] = s[0]
return s[1:]
s[1:]
s[:len(s)-1]
append
append
摘自《Go 编程语言》一书
要从切片中间删除元素,请保留顺序 在其余元素中,使用“复制”滑动编号较高的元素 元素减少 1 以填补空白:
func remove(slice []int, i int) []int { copy(slice[i:], slice[i+1:]) return slice[:len(slice)-1] }
评论
s2 = append(s2, 2)
s2
:cap(s2)
也许你可以试试这个方法:
// 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"
评论
var value reflect.Value = reflect.ValueOf(array).Elem(); value.Set(reflect.AppendSlice(value.Slice(0, index), value.Slice(index+1, value.Len())))
这有点奇怪,但这里的大多数答案都是危险的,并且掩盖了他们实际在做什么。查看有关从切片中删除项目的原始问题,正在制作切片的副本,然后填充它。这可确保在切片在程序中传递时不会引入细微的错误。
以下是一些代码,比较了此线程和原始帖子中的用户答案。这里有一个 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,并将其分配给删除索引。但是,当我们现在打印出来时,我们看到它也被修改了。这是因为切片是指向基础数组的指针。将其写出也会导致被修改,不同之处在于它更长,因为一个元素不再可从 中访问。接下来,我们更改一个值,我们可以看到也被修改了。Effective go 对此进行了更详细的介绍。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 8 9]
fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9]
}
与原始的删除索引解决方案几乎相同,但是,我们在返回之前制作了一个新切片以追加到该切片。
评论
下面是带有指针的 Playground 示例。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)
}
我采用以下方法来删除切片中的项目。这有助于提高其他人的可读性。而且也是不可变的。
func remove(items []string, item string) []string {
newitems := []string{}
for _, i := range items {
if i != item {
newitems = append(newitems, i)
}
}
return newitems
}
评论
也许这段代码会有所帮助。
它删除具有给定索引的项目。
获取数组和要删除的索引,并返回一个新数组,与 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
这就是您如何以惯用方式从切片中删除。您无需构建函数,它就内置在追加中。 在这里试试 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]
最好的方法是使用 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 := []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]
由于 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) }
...
目前 T. Claverie 投票最多的答案是正确的,但我发现如果仅在需要时执行交换,即除了切片的最后一个元素之外的所有元素,该算法会更清楚。这可以通过一个简单的 if guard 来实现。
顺序不重要/没有边界检查
func remove(s []int, i int) []int {
// bring element to remove at the end if it's not there yet
if i != len(s)-1 {
s[i] = s[len(s)-1]
}
// drop the last element
return s[:len(s)-1]
}
在语言教程中,我们了解到:
切片类似于对数组的引用。 切片不存储任何数据,它只是描述底层数组的一部分。 更改切片的元素会修改其基础数组的相应元素。
出于这个原因,在不考虑我们正在处理的值的原点和目的地的情况下在切片上使用函数是非常危险的,也是对 Go 哲学来说错误的。append
因此,正确的解决方案是使用引用到新数组的切片,而不是“主”数组。
这可以通过在构造中创建一个新切片来实现。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
}
或者更好的是,实现可以通过接口处理多种类型的函数。 但是,所有这些都是一个很好的做法,因为您构建了一个函数来执行此操作,这与提出的问题无关。
但是,可以在此处找到 Go 操场上的测试示例。
你需要稍微改变你的代码,
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。
使用泛型可以传递切片类型。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
使用软件包中的 Delete
(自 Go 1.21 起稳定,在较旧的 Go 版本上您必须导入):slices
golang.org/x/exp/slices
slice := []int{1, 2, 3, 4}
slice = slices.Delete(slice, 1, 2)
fmt.Println(slice) // [1 3 4]
slices.Delete(s, i, j)
从 s 中删除元素 s[i:j]
- 即从索引 i 到索引 i 的元素(包括索引 i)到索引 J(不包括索引 J)的元素
- 或者,如果您还记得间隔的数学符号:[i,j]
请注意两件事:
Delete
修改原始切片的内容- 您需要重新分配,否则它将具有错误的长度
slice
评论
append(slice[:s], slice[s+1:]...)
for index, item := range movies{
if item.ID == "123"{
movies = append(movies[:index], movies[index+1:]...)
break
}
}
这是从列表中删除元素的正确和最佳方法。因为其他方法可以在删除时移动元素。因此,如果顺序很重要,请尝试以下方法。
func remove(s []int, index int) []int{{ return append(s[:index], s[index+1:]...) }
基本上,您只需将相同的切片分配给您的切片,但短一个项目:
数据 = 数据[:len(data)-1]
这是用于按值删除项目
(去 1.21)
func sliceRemove[T comparable](s *[]T, rmItems ...T) {
var end bool
for {
end = true
for i, elem := range *s {
for _, rmData := range rmItems {
if elem == rmData {
*s = slices.Delete(*s, i, i+1)
end = false
break
}
}
if !end {
break
}
}
if end {
break
}
}
}
func Example_sliceRemove() {
slice := []string{"Apple", "Bar", "Foo", "Foo", "Bar", "Banana"}
sliceRemove(&slice, "Bar", "Foo")
fmt.Println(slice)
// Output:
// [Apple Banana]
}
banch标志
func sliceRemove[T comparable](s *[]T, rmItems ...T) {
var end bool
for {
end = true
for i, elem := range *s {
for _, rmData := range rmItems {
if elem == rmData {
*s = slices.Delete(*s, i, i+1)
end = false
break
}
}
if !end {
break
}
}
if end {
break
}
}
}
func BenchmarkSliceRemoveFast(b *testing.B) {
slice := []int{1, 2, 3, 2, 3, 4}
for N := 0; N < b.N; N++ {
sliceRemove(&slice, 2, 3)
}
}
func BenchmarkSliceRemoveSlow(b *testing.B) {
slice := []int{1, 2, 3, 2, 3, 4}
for N := 0; N < b.N; N++ {
rmItems := []int{2, 3}
var newSlice []int
for _, e := range slice {
if !slices.Contains(rmItems, e) {
newSlice = append(newSlice, e)
}
}
}
}
go test -v -bench=BenchmarkSliceRemove -run=none -benchmem
BenchmarkSliceRemoveFast
BenchmarkSliceRemoveFast-8 123802942 9.264 ns/op 0 B/op 0 allocs/op
BenchmarkSliceRemoveSlow
BenchmarkSliceRemoveSlow-8 19463425 89.18 ns/op 24 B/op 2 allocs/op
我在这里的两分钱,在引入库之后变得太强大了,因此:generics
lodash
mySlice = lo.Without[T](myslice, mySlice[indexOfTheElementToRemove])
其中的类型(如果它不是自定义类型,我认为它是自动推断的,因此可能不需要)。T
mySlice
T
评论