如何在下面的代码中实现deleteRecords函数:
Example:
type Record struct {
id int
name string
}
type RecordList []*Record
func deleteRecords( l *RecordList, ids []int ) {
// Assume the RecordList can contain several 100 entries.
// and the number of the of the records to be removed is about 10.
// What is the fastest and cleanest ways to remove the records that match
// the id specified in the records list.
}
答案 0 :(得分:17)
我在我的机器上做了一些微基准测试,尝试了这里的回复中给出的大多数方法,当你在id列表中得到大约40个元素时,这段代码最快出现:
func deleteRecords(data []*Record, ids []int) []*Record {
w := 0 // write index
loop:
for _, x := range data {
for _, id := range ids {
if id == x.id {
continue loop
}
}
data[w] = x
w++
}
return data[:w]
}
您没有说明保留列表中记录的顺序是否很重要。如果你不这样做,那么这个功能比上面的更快,但仍然相当干净。
func reorder(data []*Record, ids []int) []*Record {
n := len(data)
i := 0
loop:
for i < n {
r := data[i]
for _, id := range ids {
if id == r.id {
data[i] = data[n-1]
n--
continue loop
}
}
i++
}
return data[0:n]
}
随着ID数量的增加,线性搜索的成本也随之增加。大约50个元素,使用地图或进行二元搜索来查找id变得更有效率,只要您每次都可以避免重建地图(或使用列表)。在几百个ID中,即使您每次都必须重建它,使用地图或二进制搜索也会变得更有效。
如果您希望保留切片的原始内容,则更合适:
func deletePreserve(data []*Record, ids []int) []*Record {
wdata := make([]*Record, len(data))
w := 0
loop:
for _, x := range data {
for _, id := range ids {
if id == x.id {
continue loop
}
}
wdata[w] = x
w++
}
return wdata[0:w]
}
答案 1 :(得分:3)
对于个人项目,我做了类似的事情:
func filter(sl []int, fn func(int) bool) []int {
result := make([]int, 0, len(sl))
last := 0
for i, v := range sl {
if fn(v) {
result = append(result, sl[last:i]...)
last = i + 1
}
}
return append(result, sl[last:]...)
}
它不会改变原作,但应该相对有效。 这样做可能更好:
func filter(sl []int, fn func(int) bool) (result []int) {
for _, v := range sl {
if !fn(v) {
result = append(result, v)
}
}
return
}
更简单,更清洁。 如果你想在现场进行,你可能想要这样的东西:
func filter(sl []int, fn func(int) bool) []int {
outi := 0
res := sl
for _, v := range sl {
if !fn(v) {
res[outi] = v
outi++
}
}
return res[0:outi]
}
您可以优化此选项以使用copy
复制元素范围,但这是两次
代码,可能不值得。
所以,在这种特殊情况下,我可能会做类似的事情:
func deleteRecords(l []*Record, ids []int) []*Record {
outi := 0
L:
for _, v := range l {
for _, id := range ids {
if v.id == id {
continue L
}
}
l[outi] = v
outi++
}
return l[0:outi]
}
(注意:未经测试。)
没有分配,没什么花哨的,并且假设记录列表的粗略大小和您呈现的ID列表,简单的线性搜索可能会做得更好,但没有任何开销。我意识到我的版本改变了切片并且返回了一个新的切片,但这在Go中并不是非惯用的,它避免了强制调用点的切片被堆分配。
答案 2 :(得分:2)
对于你描述的情况,len(ids)大约是10,len(* l)是几百,这应该相对较快,因为它通过更新来最小化内存分配。
package main
import (
"fmt"
"strconv"
)
type Record struct {
id int
name string
}
type RecordList []*Record
func deleteRecords(l *RecordList, ids []int) {
rl := *l
for i := 0; i < len(rl); i++ {
rid := rl[i].id
for j := 0; j < len(ids); j++ {
if rid == ids[j] {
copy(rl[i:len(*l)-1], rl[i+1:])
rl[len(rl)-1] = nil
rl = rl[:len(rl)-1]
break
}
}
}
*l = rl
}
func main() {
l := make(RecordList, 777)
for i := range l {
l[i] = &Record{int(i), "name #" + strconv.Itoa(i)}
}
ids := []int{0, 1, 2, 4, 8, len(l) - 1, len(l)}
fmt.Println(ids, len(l), cap(l), *l[0], *l[1], *l[len(l)-1])
deleteRecords(&l, ids)
fmt.Println(ids, len(l), cap(l), *l[0], *l[1], *l[len(l)-1])
}
输出:
[0 1 2 4 8 776 777] 777 777 {0 name #0} {1 name #1} {776 name #776}
[0 1 2 4 8 776 777] 772 777 {1 name #1} {3 name #3} {775 name #775}
答案 3 :(得分:2)
您可以使用地图,而不是重复搜索ID。此代码预先分配了地图的完整大小,然后只是移动数组元素。没有其他分配。
func deleteRecords(l *RecordList, ids []int) {
m := make(map[int]bool, len(ids))
for _, id := range ids {
m[id] = true
}
s, x := *l, 0
for _, r := range s {
if !m[r.id] {
s[x] = r
x++
}
}
*l = s[0:x]
}
答案 4 :(得分:1)
使用vector package's Delete method作为指南,或者只使用Vector而不是切片。
答案 5 :(得分:0)
这是一个选项,但我希望有更清洁/更快更实用的功能:
func deleteRecords( l *RecordList, ids []int ) *RecordList {
var newList RecordList
for _, rec := range l {
toRemove := false
for _, id := range ids {
if rec.id == id {
toRemove = true
}
if !toRemove {
newList = append(newList, rec)
}
}
return newList
}
答案 6 :(得分:0)
如果l和id足够大,那么首先对Sort()两个列表进行排序会更有效,然后对它们进行单个循环而不是两个嵌套循环