mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-30 13:48:54 +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