Skip to content

Commit

Permalink
Merge pull request #5 from go-andiamo/add-methods
Browse files Browse the repository at this point in the history
Add methods `Append`, `Reverse` and `Slice`
  • Loading branch information
marrow16 committed Nov 22, 2022
2 parents 35e8198 + 066cb96 commit 548c4ac
Show file tree
Hide file tree
Showing 12 changed files with 1,314 additions and 54 deletions.
4 changes: 3 additions & 1 deletion accumulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package streams
// AccumulatorFunc is the function signature used to create a new Accumulator
type AccumulatorFunc[T any, R any] func(t T, r R) R

// Accumulator is the interface used Reducer
// Accumulator is the interface used Reducer to reduce to a single resultant
type Accumulator[T any, R any] interface {
// Apply adds the value of T to R, and returns the new R
Apply(t T, r R) R
}

Expand All @@ -19,6 +20,7 @@ type accumulator[T any, R any] struct {
f AccumulatorFunc[T, R]
}

// Apply adds the value of T to R, and returns the new R
func (a accumulator[T, R]) Apply(t T, r R) R {
return a.f(t, r)
}
2 changes: 1 addition & 1 deletion comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package streams

// Comparator is the interface used to compare elements of a Stream
//
// This interface is ued when sorting, when finding min/max of a stream
// This interface is used when sorting, when finding min/max of a stream
// and is also used to determine equality during set operations
// (Stream.Difference, Stream.Intersection, Stream.SymmetricDifference and Stream.Union)
type Comparator[T any] interface {
Expand Down
18 changes: 9 additions & 9 deletions comparator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,16 @@ func TestComparator_NotEqual(t *testing.T) {
require.False(t, c.NotEqual("b", "a"))
}

type comparable struct {
type testComparable struct {
primary string
secondary int
}

func TestComparator_Then(t *testing.T) {
c := NewComparator[comparable](func(v1, v2 comparable) int {
c := NewComparator[testComparable](func(v1, v2 testComparable) int {
return strings.Compare(v1.primary, v2.primary)
})
csub := NewComparator[comparable](func(v1, v2 comparable) int {
csub := NewComparator[testComparable](func(v1, v2 testComparable) int {
if v1.secondary < v2.secondary {
return -1
} else if v1.secondary > v2.secondary {
Expand All @@ -128,13 +128,13 @@ func TestComparator_Then(t *testing.T) {
})
ct := c.Then(csub)

a0 := comparable{primary: "a", secondary: 0}
a1 := comparable{primary: "a", secondary: 1}
b0 := comparable{primary: "b", secondary: 0}
b1 := comparable{primary: "b", secondary: 1}
a0 := testComparable{primary: "a", secondary: 0}
a1 := testComparable{primary: "a", secondary: 1}
b0 := testComparable{primary: "b", secondary: 0}
b1 := testComparable{primary: "b", secondary: 1}
testCases := []struct {
first comparable
second comparable
first testComparable
second testComparable
expect int
}{
{
Expand Down
4 changes: 2 additions & 2 deletions consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package streams

// Consumer is the interface used by Stream.ForEach
type Consumer[T any] interface {
// Accept is called by the user of teh consumer to supply a value
// Accept is called by the user of the consumer to supply a value
Accept(v T) error
// AndThen creates a new consumer from the current with a subsequent action to be performed
//
Expand All @@ -26,7 +26,7 @@ type consumer[T any] struct {
andThen Consumer[T]
}

// Accept is called by the user of teh consumer to supply a value
// Accept is called by the user of the consumer to supply a value
func (c consumer[T]) Accept(v T) (err error) {
if c.f != nil {
err = c.f(v)
Expand Down
108 changes: 85 additions & 23 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package streams

import (
"github.com/go-andiamo/gopt"
"reflect"
"sort"
)

// Stream is the main interface for all streams
type Stream[T any] interface {
// AllMatch returns whether all elements of this stream match the provided predicate
//
Expand All @@ -15,6 +15,8 @@ type Stream[T any] interface {
//
// if the provided predicate is nil or the stream is empty, always returns false
AnyMatch(p Predicate[T]) bool
// Append creates a new stream with all the elements of this stream followed by the specified elements
Append(items ...T) Stream[T]
// Concat creates a new stream with all the elements of this stream followed by all the elements of the added stream
Concat(add Stream[T]) Stream[T]
// Count returns the count of elements that match the provided predicate
Expand Down Expand Up @@ -85,11 +87,20 @@ type Stream[T any] interface {
//
// if no elements match in the specified position, an empty (not present) optional is returned
NthMatch(p Predicate[T], nth int) gopt.Optional[T]
// Reverse creates a new stream composed of elements from this stream but in reverse order
Reverse() Stream[T]
// Skip creates a new stream consisting of this stream after discarding the first n elements
//
// if the specified n to skip is equal to or greater than the number of elements in this stream,
// an empty stream is returned
Skip(n int) Stream[T]
// Slice creates a new stream composed of elements from this stream starting at the specified start and including
// the specified count (or to the end)
//
// the start is zero based (and less than zero is ignored)
//
// if the specified count is negative, items are selected from the start and then backwards by the count
Slice(start int, count int) Stream[T]
// Sorted creates a new stream consisting of the elements of this stream, sorted according to the provided comparator
//
// if the provided comparator is nil, the elements are not sorted
Expand Down Expand Up @@ -121,6 +132,16 @@ func Of[T any](values ...T) Stream[T] {
}
}

// OfSlice creates a new stream around a slice
//
//
// Note: Once created, If the slice changes the stream does not
func OfSlice[T any](s []T) Stream[T] {
return &stream[T]{
elements: s,
}
}

type stream[T any] struct {
elements []T
}
Expand Down Expand Up @@ -154,6 +175,13 @@ func (s *stream[T]) AnyMatch(p Predicate[T]) bool {
return false
}

// Append creates a new stream with all the elements of this stream followed by the specified elements
func (s *stream[T]) Append(items ...T) Stream[T] {
return &stream[T]{
elements: append(s.elements, items...),
}
}

// Concat creates a new stream with all the elements of this stream followed by all the elements of the added stream
func (s *stream[T]) Concat(add Stream[T]) Stream[T] {
r := &stream[T]{
Expand All @@ -164,6 +192,8 @@ func (s *stream[T]) Concat(add Stream[T]) Stream[T] {
r.elements = append(r.elements, as.elements...)
} else if sas, ok := add.(Streamable[T]); ok {
r.elements = append(r.elements, sas...)
} else if ssl, ok := add.(*streamableSlice[T]); ok {
r.elements = append(r.elements, *ssl.elements...)
} else {
_ = add.ForEach(NewConsumer(func(v T) error {
r.elements = append(r.elements, v)
Expand Down Expand Up @@ -304,7 +334,7 @@ func (s *stream[T]) Len() int {
//
// if the maximum size is greater than the length of this stream, all elements are returned
func (s *stream[T]) Limit(maxSize int) Stream[T] {
max := maxSize
max := absZero(maxSize)
if l := len(s.elements); l < max {
max = l
}
Expand Down Expand Up @@ -367,11 +397,19 @@ func (s *stream[T]) NoneMatch(p Predicate[T]) bool {
//
// if no elements match in the specified position, an empty (not present) optional is returned
func (s *stream[T]) NthMatch(p Predicate[T], nth int) gopt.Optional[T] {
absn := absInt(nth)
if absn > len(s.elements) {
return gopt.Empty[T]()
}
c := 0
if nth < 0 {
nth = 0 - nth
if p == nil && nth < 0 {
return gopt.Of[T](s.elements[len(s.elements)-absn])
} else if p == nil && nth > 0 {
return gopt.Of[T](s.elements[nth-1])
} else if nth < 0 {
nth = absn
for i := len(s.elements) - 1; i >= 0; i-- {
if p == nil || p.Test(s.elements[i]) {
if p.Test(s.elements[i]) {
c++
if c == nth {
return gopt.Of[T](s.elements[i])
Expand All @@ -380,7 +418,7 @@ func (s *stream[T]) NthMatch(p Predicate[T], nth int) gopt.Optional[T] {
}
} else if nth > 0 {
for _, v := range s.elements {
if p == nil || p.Test(v) {
if p.Test(v) {
c++
if c == nth {
return gopt.Of[T](v)
Expand All @@ -391,12 +429,24 @@ func (s *stream[T]) NthMatch(p Predicate[T], nth int) gopt.Optional[T] {
return gopt.Empty[T]()
}

// Reverse creates a new stream composed of elements from this stream but in reverse order
func (s *stream[T]) Reverse() Stream[T] {
l := len(s.elements)
r := &stream[T]{
elements: make([]T, l),
}
for i := 0; i < l; i++ {
r.elements[i] = s.elements[l-i-1]
}
return r
}

// Skip creates a new stream consisting of this stream after discarding the first n elements
//
// if the specified n to skip is equal to or greater than the number of elements in this stream,
// an empty stream is returned
func (s *stream[T]) Skip(n int) Stream[T] {
skip := n
skip := absZero(n)
if l := len(s.elements); skip >= l {
skip = l
}
Expand All @@ -405,6 +455,29 @@ func (s *stream[T]) Skip(n int) Stream[T] {
}
}

// Slice creates a new stream composed of elements from this stream starting at the specified start and including
// the specified count (or to the end)
//
// the start is zero based (and less than zero is ignored)
//
// if the specified count is negative, items are selected from the start and then backwards by the count
func (s *stream[T]) Slice(start int, count int) Stream[T] {
start = absZero(start)
end := start + count
if count < 0 {
start, end = end, start
}
if start < 0 {
start = 0
}
if end > len(s.elements) {
end = len(s.elements)
}
return &stream[T]{
elements: s.elements[start:end],
}
}

// Sorted creates a new stream consisting of the elements of this stream, sorted according to the provided comparator
//
// if the provided comparator is nil, the elements are not sorted
Expand Down Expand Up @@ -459,29 +532,18 @@ func (s *stream[T]) Unique(c Comparator[T]) Stream[T] {
if isDistinctable(vt) {
return s.Distinct()
} else if c != nil {
prevs := make(map[int]bool, len(s.elements))
pres := make([]bool, len(s.elements))
for i, v := range s.elements {
if !prevs[i] {
if !pres[i] {
for j := i + 1; j < len(s.elements); j++ {
if !prevs[j] && c.Compare(v, s.elements[j]) == 0 {
prevs[j] = true
if !pres[j] && c.Compare(v, s.elements[j]) == 0 {
pres[j] = true
}
}
prevs[i] = true
pres[i] = true
r.elements = append(r.elements, v)
}
}
}
return r
}

func isDistinctable(v any) bool {
switch v.(type) {
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, bool, float32, float64:
return true
}
if reflect.TypeOf(v).Kind() != reflect.Ptr {
return true
}
return false
}
Loading

0 comments on commit 548c4ac

Please sign in to comment.