diff --git a/README.md b/README.md index f23e4d4..66773eb 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,4 @@ problems from * [Day 135](https://github.com/vaskoz/dailycodingproblem-go/issues/280) * [Day 136](https://github.com/vaskoz/dailycodingproblem-go/issues/282) * [Day 137](https://github.com/vaskoz/dailycodingproblem-go/issues/285) +* [Day 139](https://github.com/vaskoz/dailycodingproblem-go/issues/288) diff --git a/day139/problem.go b/day139/problem.go new file mode 100644 index 0000000..7149af8 --- /dev/null +++ b/day139/problem.go @@ -0,0 +1,49 @@ +package day139 + +// Iterator is a simple iterator interface. +type Iterator interface { + Next() interface{} + HasNext() bool +} + +// PeekableIterator wraps an iterator and provides the ability to peek. +type PeekableIterator struct { + peek interface{} + peeked bool + Iter Iterator +} + +// Peek returns the next element without moving the iterator. +func (p *PeekableIterator) Peek() interface{} { + has := p.Iter.HasNext() + if !p.peeked && !has { + return nil + } else if !p.peeked { + p.peeked = true + p.peek = p.Iter.Next() + } + return p.peek +} + +// Next returns the next element and advances the iterator. +func (p *PeekableIterator) Next() interface{} { + has := p.Iter.HasNext() + var result interface{} + if p.peeked { + result = p.peek + } else if has { + result = p.Iter.Next() + } + p.peeked = false + return result +} + +// HasNext returns true if there is more to read from the iterator +// and false otherwise. +func (p *PeekableIterator) HasNext() bool { + has := p.Iter.HasNext() + if has || p.peeked { + return true + } + return false +} diff --git a/day139/problem_test.go b/day139/problem_test.go new file mode 100644 index 0000000..e354b4e --- /dev/null +++ b/day139/problem_test.go @@ -0,0 +1,89 @@ +package day139 + +import "testing" + +var testcases = []struct { + data []interface{} +}{ + {[]interface{}{3, 5, 6, 7, 9}}, + {[]interface{}{"foo", "bar", "baz"}}, +} + +type SliceIterator struct { + Slice []interface{} + pos int +} + +func (si *SliceIterator) Next() interface{} { + if si.pos >= len(si.Slice) { + return nil + } + result := si.Slice[si.pos] + si.pos++ + return result +} + +func (si *SliceIterator) HasNext() bool { + return si.pos < len(si.Slice) +} + +func TestPeekableIterator(t *testing.T) { + t.Parallel() + for _, tc := range testcases { + p := PeekableIterator{Iter: &SliceIterator{Slice: tc.data}} + for i := range tc.data { + if result := p.Peek(); result != tc.data[i] { + t.Errorf("Peek should return %v got %v", tc.data[i], result) + } + if more := p.HasNext(); !more { + t.Errorf("There should be more data") + } + if result := p.Next(); result != tc.data[i] { + t.Errorf("Next should return %v got %v", tc.data[i], result) + } + } + if result := p.Peek(); result != nil { + t.Errorf("Peek should return %v got %v", nil, result) + } + if more := p.HasNext(); more { + t.Errorf("There should be NO more data") + } + if result := p.Next(); result != nil { + t.Errorf("Next should return %v got %v", nil, result) + } + } +} + +func TestPeekableIteratorNoPeek(t *testing.T) { + t.Parallel() + for _, tc := range testcases { + p := PeekableIterator{Iter: &SliceIterator{Slice: tc.data}} + for i := range tc.data { + if more := p.HasNext(); !more { + t.Errorf("There should be more data") + } + if result := p.Next(); result != tc.data[i] { + t.Errorf("Next should return %v got %v", tc.data[i], result) + } + } + if more := p.HasNext(); more { + t.Errorf("There should be NO more data") + } + if result := p.Next(); result != nil { + t.Errorf("Next should return %v got %v", nil, result) + } + } +} + +func BenchmarkPeekableIterator(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tc := range testcases { + p := PeekableIterator{Iter: &SliceIterator{Slice: tc.data}} + for range tc.data { + p.Peek() + p.HasNext() + p.Next() + } + } + } +}