Skip to content

Commit

Permalink
feat: Initial copy paste
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley authored Nov 22, 2022
2 parents 8c71e8b + 3990979 commit b56a7f8
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
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
4 changes: 4 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
Package immutables provides immutable types.
*/
package immutables
46 changes: 46 additions & 0 deletions enumerable/concat.go
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()
}
}
105 changes: 105 additions & 0 deletions enumerable/enumerable.go
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
}
45 changes: 45 additions & 0 deletions enumerable/select.go
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()
}
38 changes: 38 additions & 0 deletions enumerable/skip.go
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()
}
76 changes: 76 additions & 0 deletions enumerable/sort.go
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()
}
33 changes: 33 additions & 0 deletions enumerable/take.go
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()
}
38 changes: 38 additions & 0 deletions enumerable/where.go
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()
}
Loading

0 comments on commit b56a7f8

Please sign in to comment.