From cd8b714dc827e1b07754d5f01ae2931c4ab5ceaf Mon Sep 17 00:00:00 2001 From: John Kerl Date: Tue, 18 Jan 2022 08:29:23 -0500 Subject: [PATCH] window-keeper util class --- .vimrc | 1 + .../pkg/transformers/utils/window-keeper.go | 70 ++++++++ .../transformers/utils/window_keeper_test.go | 157 ++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 internal/pkg/transformers/utils/window-keeper.go create mode 100644 internal/pkg/transformers/utils/window_keeper_test.go diff --git a/.vimrc b/.vimrc index d3d35005f..97c60ada4 100644 --- a/.vimrc +++ b/.vimrc @@ -1,3 +1,4 @@ map \d :w:!clear;echo Building ...; echo; make mlr map \f :w:!clear;echo Building ...; echo; make ut map \r :w:!clear;echo Building ...; echo; make ut-scan ut-mlv +map \t :w:!clear;go test github.com/johnkerl/miller/internal/pkg/transformers/... diff --git a/internal/pkg/transformers/utils/window-keeper.go b/internal/pkg/transformers/utils/window-keeper.go new file mode 100644 index 000000000..08db173e9 --- /dev/null +++ b/internal/pkg/transformers/utils/window-keeper.go @@ -0,0 +1,70 @@ +package utils + +import ( + "github.com/johnkerl/miller/internal/pkg/lib" +) + +// WindowKeeper is a sliding-window container, nominally for use by mlr step, +// for holding a number of records before the current one, the current one, and +// a number of records after. The payload is interface{}, not *mlrval.Mlrmap, +// for ease of unit-testing -- as well as since nothing here inspects the +// payload, so this code could be repurposed. +type WindowKeeper struct { + numBackward int + numForward int + + recordsBackward []interface{} + currentRecord interface{} + recordsForward []interface{} +} + +func NewWindowKeeper( + numBackward int, + numForward int, +) *WindowKeeper { + return &WindowKeeper{ + numBackward: numBackward, + numForward: numForward, + + recordsBackward: make([]interface{}, numBackward), + currentRecord: nil, + recordsForward: make([]interface{}, numForward), + } +} + +func (wk *WindowKeeper) IngestRecord( + inrec interface{}, +) { + for i := wk.numBackward - 1; i > 0; i-- { + wk.recordsBackward[i] = wk.recordsBackward[i-1] + } + if wk.numBackward > 0 { + wk.recordsBackward[0] = wk.currentRecord + } + if wk.numForward > 0 { + wk.currentRecord = wk.recordsForward[0] + for i := 0; i < wk.numForward-1; i++ { + wk.recordsForward[i] = wk.recordsForward[i+1] + } + wk.recordsForward[wk.numForward-1] = inrec + } else { + wk.currentRecord = inrec + } +} + +// GetRecord maps a user-visible indexing ..., -3, -2, -1, 0, 1, 2, 3, ... +// into this struct's zero-index array storage. +func (wk *WindowKeeper) GetRecord( + index int, +) interface{} { + if index == 0 { + return wk.currentRecord + } else if index > 0 { + lib.InternalCodingErrorIf(index > wk.numForward) + return wk.recordsForward[index-1] + } else { + index = -index + lib.InternalCodingErrorIf(index > wk.numBackward) + return wk.recordsBackward[index-1] + } +} diff --git a/internal/pkg/transformers/utils/window_keeper_test.go b/internal/pkg/transformers/utils/window_keeper_test.go new file mode 100644 index 000000000..223dc4653 --- /dev/null +++ b/internal/pkg/transformers/utils/window_keeper_test.go @@ -0,0 +1,157 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test00(t *testing.T) { + wk := NewWindowKeeper(0, 0) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(0).(string)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(0).(string)) +} + +func Test10(t *testing.T) { + wk := NewWindowKeeper(1, 0) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(0).(string)) + assert.Equal(t, nil, wk.GetRecord(-1)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(0).(string)) + assert.Equal(t, "a", wk.GetRecord(-1).(string)) + + wk.IngestRecord("c") + assert.Equal(t, "c", wk.GetRecord(0).(string)) + assert.Equal(t, "b", wk.GetRecord(-1).(string)) +} + +func Test20(t *testing.T) { + wk := NewWindowKeeper(2, 0) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(0).(string)) + assert.Equal(t, nil, wk.GetRecord(-1)) + assert.Equal(t, nil, wk.GetRecord(-2)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(0).(string)) + assert.Equal(t, "a", wk.GetRecord(-1).(string)) + assert.Equal(t, nil, wk.GetRecord(-2)) + + wk.IngestRecord("c") + assert.Equal(t, "c", wk.GetRecord(0).(string)) + assert.Equal(t, "b", wk.GetRecord(-1).(string)) + assert.Equal(t, "a", wk.GetRecord(-2).(string)) + + wk.IngestRecord("d") + assert.Equal(t, "d", wk.GetRecord(0).(string)) + assert.Equal(t, "c", wk.GetRecord(-1).(string)) + assert.Equal(t, "b", wk.GetRecord(-2).(string)) +} + +func Test01(t *testing.T) { + wk := NewWindowKeeper(0, 1) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(1).(string)) + assert.Equal(t, nil, wk.GetRecord(0)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(1).(string)) + assert.Equal(t, "a", wk.GetRecord(0).(string)) + + wk.IngestRecord("c") + assert.Equal(t, "c", wk.GetRecord(1).(string)) + assert.Equal(t, "b", wk.GetRecord(0).(string)) +} + +func Test02(t *testing.T) { + wk := NewWindowKeeper(0, 2) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(2).(string)) + assert.Equal(t, nil, wk.GetRecord(1)) + assert.Equal(t, nil, wk.GetRecord(0)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(2).(string)) + assert.Equal(t, "a", wk.GetRecord(1).(string)) + assert.Equal(t, nil, wk.GetRecord(0)) + + wk.IngestRecord("c") + assert.Equal(t, "c", wk.GetRecord(2).(string)) + assert.Equal(t, "b", wk.GetRecord(1).(string)) + assert.Equal(t, "a", wk.GetRecord(0).(string)) + + wk.IngestRecord("d") + assert.Equal(t, "d", wk.GetRecord(2).(string)) + assert.Equal(t, "c", wk.GetRecord(1).(string)) + assert.Equal(t, "b", wk.GetRecord(0).(string)) +} + +func Test32(t *testing.T) { + wk := NewWindowKeeper(3, 2) + + wk.IngestRecord("a") + assert.Equal(t, "a", wk.GetRecord(2).(string)) + assert.Equal(t, nil, wk.GetRecord(1)) + assert.Equal(t, nil, wk.GetRecord(0)) + assert.Equal(t, nil, wk.GetRecord(-1)) + assert.Equal(t, nil, wk.GetRecord(-2)) + assert.Equal(t, nil, wk.GetRecord(-3)) + + wk.IngestRecord("b") + assert.Equal(t, "b", wk.GetRecord(2).(string)) + assert.Equal(t, "a", wk.GetRecord(1).(string)) + assert.Equal(t, nil, wk.GetRecord(0)) + assert.Equal(t, nil, wk.GetRecord(-1)) + assert.Equal(t, nil, wk.GetRecord(-2)) + assert.Equal(t, nil, wk.GetRecord(-3)) + + wk.IngestRecord("c") + assert.Equal(t, "c", wk.GetRecord(2).(string)) + assert.Equal(t, "b", wk.GetRecord(1).(string)) + assert.Equal(t, "a", wk.GetRecord(0).(string)) + assert.Equal(t, nil, wk.GetRecord(-1)) + assert.Equal(t, nil, wk.GetRecord(-2)) + assert.Equal(t, nil, wk.GetRecord(-3)) + + wk.IngestRecord("d") + assert.Equal(t, "d", wk.GetRecord(2).(string)) + assert.Equal(t, "c", wk.GetRecord(1).(string)) + assert.Equal(t, "b", wk.GetRecord(0).(string)) + assert.Equal(t, "a", wk.GetRecord(-1).(string)) + assert.Equal(t, nil, wk.GetRecord(-2)) + assert.Equal(t, nil, wk.GetRecord(-3)) + + wk.IngestRecord("e") + assert.Equal(t, "e", wk.GetRecord(2).(string)) + assert.Equal(t, "d", wk.GetRecord(1).(string)) + assert.Equal(t, "c", wk.GetRecord(0).(string)) + assert.Equal(t, "b", wk.GetRecord(-1).(string)) + assert.Equal(t, "a", wk.GetRecord(-2).(string)) + assert.Equal(t, nil, wk.GetRecord(-3)) + + wk.IngestRecord("f") + assert.Equal(t, "f", wk.GetRecord(2).(string)) + assert.Equal(t, "e", wk.GetRecord(1).(string)) + assert.Equal(t, "d", wk.GetRecord(0).(string)) + assert.Equal(t, "c", wk.GetRecord(-1).(string)) + assert.Equal(t, "b", wk.GetRecord(-2).(string)) + assert.Equal(t, "a", wk.GetRecord(-3).(string)) + + wk.IngestRecord("g") + assert.Equal(t, "g", wk.GetRecord(2).(string)) + assert.Equal(t, "f", wk.GetRecord(1).(string)) + assert.Equal(t, "e", wk.GetRecord(0).(string)) + assert.Equal(t, "d", wk.GetRecord(-1).(string)) + assert.Equal(t, "c", wk.GetRecord(-2).(string)) + assert.Equal(t, "b", wk.GetRecord(-3).(string)) +}