Go:从切片中删除多个条目的最快/最干净的方法是什么?

时间:2011-02-16 19:09:40

标签: go slice

如何在下面的代码中实现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.
}

7 个答案:

答案 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()两个列表进行排序会更有效,然后对它们进行单个循环而不是两个嵌套循环