-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
425 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# immutable | ||
# Immutable | ||
A collection of core immutable types used across Source repositories |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* | ||
Package immutables provides immutable types. | ||
*/ | ||
package immutables |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package enumerable | ||
|
||
type enumerableConcat[T any] struct { | ||
sources []Enumerable[T] | ||
currentSourceIndex int | ||
} | ||
|
||
// Concat takes zero to many source `Ènumerable`s and stacks them on top | ||
// of each other, resulting in one enumerable that will iterate through all | ||
// the values in all of the given sources. | ||
func Concat[T any](sources ...Enumerable[T]) Enumerable[T] { | ||
return &enumerableConcat[T]{ | ||
sources: sources, | ||
currentSourceIndex: 0, | ||
} | ||
} | ||
|
||
func (s *enumerableConcat[T]) Next() (bool, error) { | ||
for { | ||
if s.currentSourceIndex >= len(s.sources) { | ||
return false, nil | ||
} | ||
|
||
currentSource := s.sources[s.currentSourceIndex] | ||
hasValue, err := currentSource.Next() | ||
if err != nil { | ||
return false, nil | ||
} | ||
if hasValue { | ||
return true, nil | ||
} | ||
|
||
s.currentSourceIndex += 1 | ||
} | ||
} | ||
|
||
func (s *enumerableConcat[T]) Value() T { | ||
return s.sources[s.currentSourceIndex].Value() | ||
} | ||
|
||
func (s *enumerableConcat[T]) Reset() { | ||
s.currentSourceIndex = 0 | ||
for _, source := range s.sources { | ||
source.Reset() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package enumerable | ||
|
||
// Enumerable represents a set of elements that can be iterated through | ||
// multiple times. | ||
// | ||
// The enumerable may be a composite of multiple actions that will be lazily | ||
// executed upon iteration, allowing the enumerable to be constructed out of a | ||
// complex set of instructions that can be evaluated in a single iteration of the | ||
// underlying set. | ||
type Enumerable[T any] interface { | ||
// Next attempts to evaluate the next item in the enumeration - allowing its | ||
// exposure via the `Value()` function. | ||
// | ||
// It will return false if it has reached the end of the enumerable, and/or an | ||
// error if one was generated during evaluation. | ||
Next() (bool, error) | ||
|
||
// Value returns the current item in the enumeration. It does not progress the | ||
// enumeration, and should be a simple getter. | ||
// | ||
// If the previous Next call did not return true, or Next has never been called | ||
// the behaviour and return value of this function is undefined. | ||
Value() T | ||
|
||
// Reset resets the enumerable, allowing for re-iteration. | ||
Reset() | ||
} | ||
|
||
type enumerableSlice[T any] struct { | ||
source []T | ||
currentIndex int | ||
maxIndex int | ||
} | ||
|
||
// New creates an `Enumerable` from the given slice. | ||
func New[T any](source []T) Enumerable[T] { | ||
return &enumerableSlice[T]{ | ||
source: source, | ||
currentIndex: -1, | ||
maxIndex: len(source) - 1, | ||
} | ||
} | ||
|
||
func (s *enumerableSlice[T]) Next() (bool, error) { | ||
if s.currentIndex == s.maxIndex { | ||
return false, nil | ||
} | ||
s.currentIndex += 1 | ||
return true, nil | ||
} | ||
|
||
func (s *enumerableSlice[T]) Value() T { | ||
return s.source[s.currentIndex] | ||
} | ||
|
||
func (s *enumerableSlice[T]) Reset() { | ||
s.currentIndex = -1 | ||
} | ||
|
||
// ForEach iterates over the given source `Enumerable` performing the given | ||
// action on each item. It resets the source `Enumerable` on completion. | ||
func ForEach[T any](source Enumerable[T], action func(item T)) error { | ||
for { | ||
hasNext, err := source.Next() | ||
if err != nil { | ||
return err | ||
} | ||
if !hasNext { | ||
break | ||
} | ||
item := source.Value() | ||
action(item) | ||
} | ||
source.Reset() | ||
return nil | ||
} | ||
|
||
// OnEach iterates over the given source `Enumerable` performing the given | ||
// action for each item yielded. It resets the source `Enumerable` on completion. | ||
func OnEach[T any](source Enumerable[T], action func()) error { | ||
for { | ||
hasNext, err := source.Next() | ||
if err != nil { | ||
return err | ||
} | ||
if !hasNext { | ||
break | ||
} | ||
action() | ||
} | ||
source.Reset() | ||
return nil | ||
} | ||
|
||
// TryGetFirst returns the first element yielded from the given source along with true. | ||
// If no items are yielded by the source, then false with be returned. Any errors generated | ||
// during enumeration will be yielded instead of a value. | ||
func TryGetFirst[T any](source Enumerable[T]) (T, bool, error) { | ||
hasNext, err := source.Next() | ||
if err != nil || !hasNext { | ||
var defaultV T | ||
return defaultV, false, err | ||
} | ||
return source.Value(), true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package enumerable | ||
|
||
type enumerableSelect[TSource any, TResult any] struct { | ||
source Enumerable[TSource] | ||
selector func(TSource) (TResult, error) | ||
currentValue TResult | ||
} | ||
|
||
// Select creates a new `Enumerable` that iterates through each item | ||
// yielded by the given source and then yields the value returned by | ||
// the given selector. | ||
func Select[TSource any, TResult any]( | ||
source Enumerable[TSource], | ||
selector func(TSource) (TResult, error), | ||
) Enumerable[TResult] { | ||
return &enumerableSelect[TSource, TResult]{ | ||
source: source, | ||
selector: selector, | ||
} | ||
} | ||
|
||
func (s *enumerableSelect[TSource, TResult]) Next() (bool, error) { | ||
hasNext, err := s.source.Next() | ||
if !hasNext || err != nil { | ||
return hasNext, err | ||
} | ||
|
||
value := s.source.Value() | ||
// We do this here to keep the work (and errors) in the `Next` call | ||
result, err := s.selector(value) | ||
if err != nil { | ||
return false, nil | ||
} | ||
|
||
s.currentValue = result | ||
return true, nil | ||
} | ||
|
||
func (s *enumerableSelect[TSource, TResult]) Value() TResult { | ||
return s.currentValue | ||
} | ||
|
||
func (s *enumerableSelect[TSource, TResult]) Reset() { | ||
s.source.Reset() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package enumerable | ||
|
||
type enumerableSkip[T any] struct { | ||
source Enumerable[T] | ||
offset uint64 | ||
count uint64 | ||
} | ||
|
||
// Skip creates an `Enumerable` from the given `Enumerable` and offset. The returned | ||
// `Enumerable` will skip through items until the number of items yielded from source | ||
// excedes the give offset. | ||
func Skip[T any](source Enumerable[T], offset uint64) Enumerable[T] { | ||
return &enumerableSkip[T]{ | ||
source: source, | ||
offset: offset, | ||
} | ||
} | ||
|
||
func (s *enumerableSkip[T]) Next() (bool, error) { | ||
for s.count < s.offset { | ||
s.count += 1 | ||
hasNext, err := s.source.Next() | ||
if !hasNext || err != nil { | ||
return hasNext, err | ||
} | ||
} | ||
s.count += 1 | ||
return s.source.Next() | ||
} | ||
|
||
func (s *enumerableSkip[T]) Value() T { | ||
return s.source.Value() | ||
} | ||
|
||
func (s *enumerableSkip[T]) Reset() { | ||
s.count = 0 | ||
s.source.Reset() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package enumerable | ||
|
||
import "sort" | ||
|
||
type enumerableSort[T any] struct { | ||
source Enumerable[T] | ||
less func(T, T) bool | ||
capacity int | ||
result Enumerable[T] | ||
} | ||
|
||
// Sort creates an `Enumerable` from the given `Enumerable`, using the given | ||
// less function to determine as to whether an item is less than the other in | ||
// in terms of order. | ||
// | ||
// The returned `Enumerable` will enumerate the entire source | ||
// enumerable on the first `Next` call, but will not enumerate it again unless | ||
// reset. | ||
func Sort[T any](source Enumerable[T], less func(T, T) bool, capacity int) Enumerable[T] { | ||
return &enumerableSort[T]{ | ||
source: source, | ||
less: less, | ||
capacity: capacity, | ||
} | ||
} | ||
|
||
func (s *enumerableSort[T]) Next() (bool, error) { | ||
if s.result == nil { | ||
result := make([]T, 0, s.capacity) | ||
// Declaring an anonymous function costs, so we do it here outside of the loop | ||
// even though it is slightly less intuitive | ||
f := func(i int) bool { | ||
return !s.less(result[i], s.source.Value()) | ||
} | ||
|
||
for i := 0; i <= s.capacity; i++ { | ||
hasNext, err := s.source.Next() | ||
if err != nil { | ||
return false, err | ||
} | ||
if !hasNext { | ||
break | ||
} | ||
|
||
previousLength := len(result) | ||
indexOfFirstGreaterValue := sort.Search(previousLength, f) | ||
value := s.source.Value() | ||
result = append(result, value) | ||
if indexOfFirstGreaterValue == previousLength { | ||
// Value is the greatest, and belongs at the end | ||
continue | ||
} | ||
// Shift all items to the right of the first element of greater value by | ||
// one place. This call should not allocate. | ||
copy(result[indexOfFirstGreaterValue+1:], result[indexOfFirstGreaterValue:]) | ||
result[indexOfFirstGreaterValue] = value | ||
} | ||
|
||
// Use the enumerableSlice for convienience | ||
s.result = New(result) | ||
} | ||
|
||
return s.result.Next() | ||
} | ||
|
||
func (s *enumerableSort[T]) Value() T { | ||
return s.result.Value() | ||
} | ||
|
||
func (s *enumerableSort[T]) Reset() { | ||
// s.result should be cleared, not reset, as Reset should | ||
// enable the re-enumeration of the entire enumeration chain, | ||
// not just the last step. | ||
s.result = nil | ||
s.source.Reset() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package enumerable | ||
|
||
type enumerableTake[T any] struct { | ||
source Enumerable[T] | ||
limit uint64 | ||
count uint64 | ||
} | ||
|
||
// Take creates an `Enumerable` from the given `Enumerable` and limit. The returned | ||
// `Enumerable` will restrict the maximum number of items yielded to the given limit. | ||
func Take[T any](source Enumerable[T], limit uint64) Enumerable[T] { | ||
return &enumerableTake[T]{ | ||
source: source, | ||
limit: limit, | ||
} | ||
} | ||
|
||
func (s *enumerableTake[T]) Next() (bool, error) { | ||
if s.count == s.limit { | ||
return false, nil | ||
} | ||
s.count += 1 | ||
return s.source.Next() | ||
} | ||
|
||
func (s *enumerableTake[T]) Value() T { | ||
return s.source.Value() | ||
} | ||
|
||
func (s *enumerableTake[T]) Reset() { | ||
s.count = 0 | ||
s.source.Reset() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package enumerable | ||
|
||
type enumerableWhere[T any] struct { | ||
source Enumerable[T] | ||
predicate func(T) (bool, error) | ||
} | ||
|
||
// Where creates an `Enumerable` from the given `Enumerable` and predicate. Items in the | ||
// source `Enumerable` must return true when passed into the predicate in order to be yielded | ||
// from the returned `Enumerable`. | ||
func Where[T any](source Enumerable[T], predicate func(T) (bool, error)) Enumerable[T] { | ||
return &enumerableWhere[T]{ | ||
source: source, | ||
predicate: predicate, | ||
} | ||
} | ||
|
||
func (s *enumerableWhere[T]) Next() (bool, error) { | ||
for { | ||
hasNext, err := s.source.Next() | ||
if !hasNext || err != nil { | ||
return hasNext, err | ||
} | ||
|
||
value := s.source.Value() | ||
if passes, err := s.predicate(value); passes || err != nil { | ||
return passes, err | ||
} | ||
} | ||
} | ||
|
||
func (s *enumerableWhere[T]) Value() T { | ||
return s.source.Value() | ||
} | ||
|
||
func (s *enumerableWhere[T]) Reset() { | ||
s.source.Reset() | ||
} |
Oops, something went wrong.