Skip to content

Commit

Permalink
deque: new util package replacing prepend
Browse files Browse the repository at this point in the history
  • Loading branch information
zephyrtronium committed Aug 22, 2024
1 parent 3a04261 commit 1d1dca0
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 203 deletions.
10 changes: 5 additions & 5 deletions brain/kvbrain/speak.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import (
"github.com/dgraph-io/badger/v4"

"github.com/zephyrtronium/robot/brain"
"github.com/zephyrtronium/robot/prepend"
"github.com/zephyrtronium/robot/deque"
"github.com/zephyrtronium/robot/tpool"
)

var prependerPool tpool.Pool[*prepend.List[string]]
var prependerPool tpool.Pool[deque.Deque[string]]

// Speak generates a full message and appends it to w.
// The prompt is in reverse order and has entropy reduction applied.
func (br *Brain) Speak(ctx context.Context, tag string, prompt []string, w *brain.Builder) error {
search := prependerPool.Get().Set(prompt...)
defer func() { prependerPool.Put(search) }()
search := prependerPool.Get().Prepend(prompt...)
defer func() { prependerPool.Put(search.Reset()) }()

tb := hashTag(make([]byte, 0, tagHashLen), tag)
b := make([]byte, 0, 128)
Expand All @@ -41,7 +41,7 @@ func (br *Brain) Speak(ctx context.Context, tag string, prompt []string, w *brai
break
}
w.Append(id, b)
search = search.Drop(search.Len() - l - 1).Prepend(brain.ReduceEntropy(string(b)))
search = search.DropEnd(search.Len() - l - 1).Prepend(brain.ReduceEntropy(string(b)))
}
return nil
}
Expand Down
10 changes: 5 additions & 5 deletions brain/sqlbrain/speak.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import (
"zombiezen.com/go/sqlite"

"github.com/zephyrtronium/robot/brain"
"github.com/zephyrtronium/robot/prepend"
"github.com/zephyrtronium/robot/deque"
"github.com/zephyrtronium/robot/tpool"
)

var prependerPool tpool.Pool[*prepend.List[string]]
var prependerPool tpool.Pool[deque.Deque[string]]

// Speak generates a full message and appends it to w.
// The prompt is in reverse order and has entropy reduction applied.
func (br *Brain) Speak(ctx context.Context, tag string, prompt []string, w *brain.Builder) error {
search := prependerPool.Get().Set(prompt...)
defer func() { prependerPool.Put(search) }()
search := prependerPool.Get().Prepend(prompt...)
defer func() { prependerPool.Put(search.Reset()) }()

conn, err := br.db.Take(ctx)
defer br.db.Put(conn)
Expand All @@ -39,7 +39,7 @@ func (br *Brain) Speak(ctx context.Context, tag string, prompt []string, w *brai
break
}
w.Append(id, b)
search = search.Drop(search.Len() - l - 1).Prepend(brain.ReduceEntropy(string(b)))
search = search.DropEnd(search.Len() - l - 1).Prepend(brain.ReduceEntropy(string(b)))
}
return nil
}
Expand Down
90 changes: 90 additions & 0 deletions deque/deque.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Package deque provides a slice-backed double-ended queue.
package deque

import "slices"

// Deque is a slice-backed double-ended queue.
type Deque[Elem any] struct {
el []Elem
// left is the position of the leftmost valid element in el.
// left >= len(el) implies the ring is empty.
left int
}

// Len returns the number of elements in the deque.
func (d Deque[Elem]) Len() int {
return len(d.el) - d.left
}

// Append adds elements tot he end of the deque.
func (d Deque[Elem]) Append(ee ...Elem) Deque[Elem] {
d.el = append(d.el, ee...)
return d
}

// Prepend adds elements to the front of the deque.
func (d Deque[Elem]) Prepend(ee ...Elem) Deque[Elem] {
d = d.GrowFront(len(ee))
d.left -= len(ee)
copy(d.Slice(), ee)
return d
}

// GrowFront ensures there is space to [Prepend] at least n elements.
func (d Deque[Elem]) GrowFront(n int) Deque[Elem] {
if d.left >= n {
return d
}
// Grow the slice, then slide the existing elements to the end.
k := d.Len()
d.el = slices.Grow(d.el, n)
copy(d.el[cap(d.el)-k:cap(d.el)], d.Slice())
d.el = d.el[:cap(d.el)]
d.left = cap(d.el) - k
return d
}

// GrowEnd ensures there is space to [Append] at least n elements.
func (d Deque[Elem]) GrowEnd(n int) Deque[Elem] {
d.el = slices.Grow(d.el, n)
return d
}

// DropEnd removes n elements from the end of the deque.
// If n is negative, there is no change.
// If n is larger than the deque's size, the result is empty.
func (d Deque[Elem]) DropEnd(n int) Deque[Elem] {
if n <= 0 {
return d
}
if n >= d.Len() {
return d.Reset()
}
d.el = d.el[:len(d.el)-n]
return d
}

// DropEndWhile removes elements from the end of the deque until the predicate
// returns false.
// The pointer passed to the predicate is a view into the deque's memory.
func (d Deque[Elem]) DropEndWhile(pred func(Elem) bool) Deque[Elem] {
for len(d.el) > d.left {
if !pred(d.el[len(d.el)-1]) {
break
}
d.el = d.el[:len(d.el)-1]
}
return d
}

// Reset removes all elements from the deque.
func (d Deque[Elem]) Reset() Deque[Elem] {
d.left = len(d.el)
return d
}

// Slice returns a view into the deque's memory.
// Elements prepended to the deque appear at the beginning of the slice.
func (d Deque[Elem]) Slice() []Elem {
return d.el[d.left:]
}
104 changes: 104 additions & 0 deletions deque/deque_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package deque_test

import (
"slices"
"testing"

"github.com/zephyrtronium/robot/deque"
)

func TestDeque(t *testing.T) {
cases := []struct {
name string
append []int
prepend []int
want []int
}{
{
name: "empty",
append: nil,
prepend: nil,
want: nil,
},
{
name: "append",
append: []int{1, 2},
prepend: nil,
want: []int{1, 2},
},
{
name: "prepend",
append: nil,
prepend: []int{1, 2},
want: []int{1, 2},
},
{
name: "both",
append: []int{1, 2},
prepend: []int{3, 4},
want: []int{3, 4, 1, 2},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
var d deque.Deque[int]
invariants := func() {
if d.Len() != len(d.Slice()) {
t.Errorf("lens disagree: d.Len gave %d, len(d.Slice) gave %d", d.Len(), len(d.Slice()))
}
}
invariants()
d = d.Append(c.append...)
invariants()
d = d.Prepend(c.prepend...)
invariants()
if !slices.Equal(d.Slice(), c.want) {
t.Errorf("wrong result: want %v, got %v", c.want, d.Slice())
}
})
}
}

func TestDropEndWhile(t *testing.T) {
cases := []struct {
name string
start []bool
want []bool
}{
{
name: "empty",
start: nil,
want: nil,
},
{
name: "none",
start: []bool{false, false},
want: []bool{false, false},
},
{
name: "one",
start: []bool{false, true},
want: []bool{false},
},
{
name: "end",
start: []bool{true, false},
want: []bool{true, false},
},
{
name: "all",
start: []bool{true, true},
want: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
d := deque.Deque[bool]{}.Append(c.start...)
d = d.DropEndWhile(func(b bool) bool { return b })
if !slices.Equal(d.Slice(), c.want) {
t.Errorf("wrong result: want %v, got %v", c.want, d.Slice())
}
})
}
}
83 changes: 0 additions & 83 deletions prepend/prepend.go

This file was deleted.

Loading

0 comments on commit 1d1dca0

Please sign in to comment.