只留下切片中的n个元素

时间:2017-10-23 14:43:39

标签: go slice

请参阅this playground。如您所见,我在结构中有一个切片。我还有一个方法可用于向切片添加新元素。这很好。

但现在我的问题是我想扩展该方法,以便它留下切片的n个元素。因此,在添加新元素时,最老的"应该删除,并添加新的。

我该怎么做? Aren有没有我可以使用的开箱即用的包裹?

3 个答案:

答案 0 :(得分:1)

如果要从切片s中删除第一个元素(那里已经存在的元素最长的元素),您可以简单地执行s = s[1:],从第一个开始对切片进行sa引用旧切片的元素。

我修改了你的代码来执行此操作:

https://play.golang.org/p/Eu-KLoinz0

package main

import (
    "fmt"
    "time"
)

type Statistics struct {
    LastScan time.Time
    Imports  []Import
}

type Import struct {
    text string
}

func (s *Statistics) AddImport(i Import) {
    s.Imports = append(s.Imports, i)
    const max = 2
    if len(s.Imports) > max {
        s.Imports = s.Imports[len(s.Imports)-max:]
    }
}

func main() {
    s := Statistics{}
    s.AddImport(Import{text: "myText1"})
    fmt.Println(s.Imports)
    s.AddImport(Import{text: "myText2"})
    fmt.Println(s.Imports)
    s.AddImport(Import{text: "myText3"})
    fmt.Println(s.Imports)
    s.AddImport(Import{text: "myText4"})
    fmt.Println(s.Imports)
}

答案 1 :(得分:1)

子片。语法slice[n:m]将输入切片的部分从n返回到m-1。可以省略任何一个分别暗示0或len(切片)。所以slice[n:]表示"给我从n到结尾的切片部分。 slice[len(slice)-n:]将为您提供切片中的最后n个条目。

https://play.golang.org/p/4JRcRH-wc3

func (s *Statistics) AddImport(i Import) {
    // How can I optimize this method so that
    // only the last two entries are kept?

    s.Imports = append(s.Imports, i)
    if len(s.Imports) > numberToKeep {
        s.Imports = s.Imports[len(s.Imports)-numberToKeep:]
    }
}

请注意,这不会从内存中删除切片的开头(或允许它被垃圾回收),但是当您继续添加条目时,运行时将自动分配更大的底层数组并复制内容,释放早期数组分配是垃圾收集。它目前在2倍的基础上执行此操作(因此每次切片的大小增加时都会这样做),尽管这没有记录,因此无法保证保持这种状态。如果内存管理对您很重要,您可以使用内置的copy(dest, source)函数手动将切片复制到新的后备阵列。

高级选项包括使用队列系统(通常使用链接列表格式实现)或圆形切片(其中条目"包围"从末尾到数组开头的未使用索引,另一种常见的队列设计)。

答案 2 :(得分:1)

例如,

addimport.go

package main

import (
    "fmt"
    "time"
)

type Statistics struct {
    LastScan time.Time
    Imports  []Import
}

type Import struct {
    text string
}

func (s *Statistics) AddImport(i Import) {
    // only the last n entries are kept
    const n = 2 // n > 0 and small
    if len(s.Imports) >= n {
        copy(s.Imports, s.Imports[len(s.Imports)-n+1:])
        s.Imports = s.Imports[:n-1]
    }
    s.Imports = append(s.Imports, i)
}

func main() {
    s := Statistics{}
    fmt.Println(len(s.Imports), cap(s.Imports), s.Imports)
    s.AddImport(Import{text: "myText1"})
    s.AddImport(Import{text: "myText2"})
    s.AddImport(Import{text: "myText3"})
    fmt.Println(len(s.Imports), cap(s.Imports), s.Imports)
}

游乐场:https://play.golang.org/p/204-uB8Zls

输出:

0 0 []
2 2 [{myText2} {myText3}]

代码应该合理有效。 Go有一个基准测试包。以下是peterSO,Kaedys和gonutz解决方案的基准测试结果。

$ go test -bench=. addimport_test.go
BenchmarkAddImport/PeterSO-4   100000   16145 ns/op      96 B/op     3 allocs/op
BenchmarkAddImport/Kaedys-4     30000   59344 ns/op   32032 B/op   502 allocs/op
BenchmarkAddImport/Gonutz-4     30000   60447 ns/op   32032 B/op   502 allocs/op

addimport_test.go

package main

import (
    "testing"
    "time"
)

type Statistics struct {
    LastScan time.Time
    Imports  []Import
}

type Import struct {
    text string
}

func (s *Statistics) AddImportPeterSO(i Import) {
    // only the last n entries are kept
    const n = 2 // n > 0 and small
    if len(s.Imports) >= n {
        copy(s.Imports, s.Imports[len(s.Imports)-n+1:])
        s.Imports = s.Imports[:n-1]
    }
    s.Imports = append(s.Imports, i)
}

const numberToKeep = 2

func (s *Statistics) AddImportKaedys(i Import) {
    s.Imports = append(s.Imports, i)
    if len(s.Imports) > numberToKeep {
        s.Imports = s.Imports[len(s.Imports)-numberToKeep:]
    }
}

func (s *Statistics) AddImportGonutz(i Import) {
    s.Imports = append(s.Imports, i)
    const max = 2
    if len(s.Imports) > max {
        s.Imports = s.Imports[1:]
    }
}

func benchmarkAddImport(b *testing.B, addImport func(*Statistics, Import)) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        var s Statistics
        for j := 0; j < 1000; j++ {
            addImport(&s, Import{})
        }
    }
}

func BenchmarkAddImport(b *testing.B) {
    b.Run("PeterSO", func(b *testing.B) {
        benchmarkAddImport(b, (*Statistics).AddImportPeterSO)
    })
    b.Run("Kaedys", func(b *testing.B) {
        benchmarkAddImport(b, (*Statistics).AddImportKaedys)
    })
    b.Run("Gonutz", func(b *testing.B) {
        benchmarkAddImport(b, (*Statistics).AddImportGonutz)
    })
}

游乐场:https://play.golang.org/p/Q2X_T5Vofe

此问题的一般形式是循环缓冲区:Circular buffer