切片中只留下n个元素

问题描述 投票:0回答:3

请参阅这个游乐场。正如你所看到的,我在结构中有一个切片。我还有一个可用于向切片添加新元素的方法。这很好用。

但现在我的问题是我想扩展该方法,以便它留下切片的

n
元素。因此,当添加新元素时,应删除“最旧的”元素并添加新元素。

我该怎么做?没有我可以使用的开箱即用的软件包吗?

go slice
3个回答
3
投票

例如,

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.


3
投票

如果您想从切片中删除第一个元素(其中最长的元素)

s
,您可以简单地执行
s = s[1:]
,这使得 s 成为对从旧切片的第一个元素开始的切片的引用.

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

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)
}

2
投票

子切片。语法

slice[n:m]
返回输入切片从 n 到 m-1 的部分。可以省略其中之一来分别表示 0 或 len(slice)。所以
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)
函数手动将切片复制到新的后备数组。

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

© www.soinside.com 2019 - 2024. All rights reserved.