mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-28 13:48:51 +08:00
Generalizing a tracker of attribute ranges.
This commit is contained in:
parent
f16bda1131
commit
4e705950f2
94
attrrange/attrrange.go
Normal file
94
attrrange/attrrange.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Package attrrange simplifies tracking of attributes that apply to a range of
|
||||
// items.
|
||||
// Refer to the examples if the test file for details on usage.
|
||||
package attrrange
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// AttrRange is a range of items that share the same attributes.
|
||||
type AttrRange struct {
|
||||
// Low is the first position where these attributes apply.
|
||||
Low int
|
||||
|
||||
// High is the end of the range. The attributes apply to all items in range
|
||||
// Low <= b < high.
|
||||
High int
|
||||
|
||||
// AttrIdx is the index of the attributes that apply to this range.
|
||||
AttrIdx int
|
||||
}
|
||||
|
||||
// newAttrRange returns a new AttrRange instance.
|
||||
func newAttrRange(low, high, attrIdx int) *AttrRange {
|
||||
return &AttrRange{
|
||||
Low: low,
|
||||
High: high,
|
||||
AttrIdx: attrIdx,
|
||||
}
|
||||
}
|
||||
|
||||
// Tracker tracks attributes that apply to a range of items.
|
||||
// This object is not thread safe.
|
||||
type Tracker struct {
|
||||
// ranges maps low indices of ranges to the attribute ranges.
|
||||
ranges map[int]*AttrRange
|
||||
}
|
||||
|
||||
// NewTracker returns a new tracker of ranges that share the same attributes.
|
||||
func NewTracker() *Tracker {
|
||||
return &Tracker{
|
||||
ranges: map[int]*AttrRange{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a new range of items that share attributes with the specified
|
||||
// index.
|
||||
// The low position of the range must not overlap with low position of any
|
||||
// existing range.
|
||||
func (t *Tracker) Add(low, high, attrIdx int) error {
|
||||
ar := newAttrRange(low, high, attrIdx)
|
||||
if ar, ok := t.ranges[low]; ok {
|
||||
return fmt.Errorf("already have range starting on low:%d, existing:%+v", low, ar)
|
||||
}
|
||||
t.ranges[low] = ar
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrNotFound indicates that the requested position wasn't found in any of the
|
||||
// known ranges.
|
||||
var ErrNotFound = errors.New("range not found")
|
||||
|
||||
// ForPosition returns attribute index that apply to the specified position.
|
||||
// Returns ErrNotFound when the requested position wasn't found in any of the
|
||||
// known ranges.
|
||||
func (t *Tracker) ForPosition(pos int) (*AttrRange, error) {
|
||||
if ar, ok := t.ranges[pos]; ok {
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
var keys []int
|
||||
for k := range t.ranges {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
|
||||
var res *AttrRange
|
||||
for _, k := range keys {
|
||||
ar := t.ranges[k]
|
||||
if ar.Low > pos {
|
||||
break
|
||||
}
|
||||
if ar.High > pos {
|
||||
res = ar
|
||||
}
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return res, nil
|
||||
}
|
118
attrrange/attrrange_test.go
Normal file
118
attrrange/attrrange_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
package attrrange
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestForPosition(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
// if not nil, called before calling ForPosition.
|
||||
// Can add ranges.
|
||||
update func(*Tracker) error
|
||||
pos int
|
||||
want *AttrRange
|
||||
wantErr error
|
||||
wantUpdateErr bool
|
||||
}{
|
||||
{
|
||||
desc: "fails when no ranges given",
|
||||
pos: 0,
|
||||
wantErr: ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "fails to add a duplicate",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(2, 3, 41)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when multiple given ranges, position falls before them",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(5, 10, 41)
|
||||
},
|
||||
pos: 1,
|
||||
wantErr: ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "multiple given options, position falls on the lower",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(5, 10, 41)
|
||||
},
|
||||
pos: 2,
|
||||
want: newAttrRange(2, 5, 40),
|
||||
},
|
||||
{
|
||||
desc: "multiple given options, position falls between them",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(5, 10, 41)
|
||||
},
|
||||
pos: 4,
|
||||
want: newAttrRange(2, 5, 40),
|
||||
},
|
||||
{
|
||||
desc: "multiple given options, position falls on the higher",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(5, 10, 41)
|
||||
},
|
||||
pos: 5,
|
||||
want: newAttrRange(5, 10, 41),
|
||||
},
|
||||
{
|
||||
desc: "multiple given options, position falls after them",
|
||||
update: func(tr *Tracker) error {
|
||||
if err := tr.Add(2, 5, 40); err != nil {
|
||||
return err
|
||||
}
|
||||
return tr.Add(5, 10, 41)
|
||||
},
|
||||
pos: 10,
|
||||
wantErr: ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
tr := NewTracker()
|
||||
if tc.update != nil {
|
||||
err := tc.update(tr)
|
||||
if (err != nil) != tc.wantUpdateErr {
|
||||
t.Errorf("tc.update => unexpected error:%v, wantUpdateErr:%v", err, tc.wantUpdateErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
got, err := tr.ForPosition(tc.pos)
|
||||
if err != tc.wantErr {
|
||||
t.Errorf("ForPosition => unexpected error:%v, wantErr:%v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Errorf("ForPosition => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user