Search code examples
goslice

Only leave n elements in slice


Please see this playground. As you can see I have a slice within a struct. I also have a method which can be used to add a new element to the slice. This works fine.

But now my problem is that I want to extend the method so that it leaves n elements of the slice. So when adding a new element, the "oldest" should be removed and the new one should be added.

How can I do this? Aren't there out-of-the-box packages which I can use?


Solution

  • For example,

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

    Playground: https://play.golang.org/p/204-uB8Zls

    Output:

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

    Code should be reasonably efficient. Go has a benchmark package. Here are the benchmark results for the solutions from peterSO, Kaedys, and 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)
        })
    }
    

    Playground: https://play.golang.org/p/Q2X_T5Vofe


    The general form of this problem is a circular buffer: Circular buffer.