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,然后将其分配为删除索引。但是,当我们现在打印全部内容时,我们发现它也已被修改。这是因为切片是指向基础数组的指针。将其写出到
以下示例我将不涉及,但出于我们的目的,它会做同样的事情。只是说明使用复制没有什么不同。
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] } |
如您所见,此输出的行为符合大多数人的期望,也可能是大多数人想要的。修改
这段代码有点冗长,因此上面的代码可以更改为此代码。
正确答案
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" |
- 可能是因为它不是针对问题的惯用语言和过度设计的。解决它是一种有趣的方法,但是没有人应该使用它。