github.com/haraldrudell/parl@v0.4.176/iters/integer-iterator.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package iters
     7  
     8  import (
     9  	"sync"
    10  	"sync/atomic"
    11  
    12  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    13  	"golang.org/x/exp/constraints"
    14  )
    15  
    16  type IntegerIterator[T constraints.Integer] struct {
    17  	lastValue, delta T
    18  	// isEnd is fast outside-lock check for no values available
    19  	isEnd atomic.Bool
    20  
    21  	lock sync.Mutex
    22  	//	- behind lock
    23  	didReturnAValue bool
    24  	//	- behind lock
    25  	value T
    26  }
    27  
    28  func NewIntegerIterator[T constraints.Integer](firstValue, lastValue T) (iterator Iterator[T]) {
    29  	i := &IntegerIterator[T]{value: firstValue, lastValue: lastValue}
    30  	if firstValue > lastValue {
    31  		var i64 = int64(-1)
    32  		i.delta = T(i64)
    33  	} else {
    34  		i.delta = T(1)
    35  	}
    36  	return i
    37  }
    38  
    39  // Init implements the right-hand side of a short variable declaration in
    40  // the init statement of a Go “for” clause
    41  //
    42  //		for i, iterator := iters.NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    43  //	   // i is pointer to slice element
    44  func (i *IntegerIterator[T]) Init() (iterationVariable T, iterator Iterator[T]) { iterator = i; return }
    45  
    46  // Cond implements the condition statement of a Go “for” clause
    47  //   - condition is true if iterationVariable was assigned a value and the iteration should continue
    48  //   - the iterationVariable is updated by being provided as a pointer.
    49  //     iterationVariable cannot be nil
    50  //   - errp is an optional error pointer receiving any errors during iterator execution
    51  //
    52  // Usage:
    53  //
    54  //	for i, iterator := iters.NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    55  //	  // i is pointer to slice element
    56  func (i *IntegerIterator[T]) Cond(iterationVariablep *T, errp ...*error) (condition bool) {
    57  	if iterationVariablep == nil {
    58  		cyclebreaker.NilError("iterationVariablep")
    59  	}
    60  
    61  	// check for next value
    62  	var value T
    63  	if value, condition = i.nextSame(IsNext); condition {
    64  		*iterationVariablep = value
    65  	}
    66  
    67  	return // condition and iterationVariablep updated, errp unchanged
    68  }
    69  
    70  // Next advances to next item and returns it
    71  //   - if hasValue true, value contains the next value
    72  //   - otherwise, no more items exist and value is the data type zero-value
    73  func (i *IntegerIterator[T]) Next() (value T, hasValue bool) { return i.nextSame(IsNext) }
    74  
    75  // Same returns the same value again
    76  //   - if hasValue true, value is valid
    77  //   - otherwise, no more items exist and value is the data type zero-value
    78  //   - If Next or Cond has not been invoked, Same first advances to the first item
    79  func (i *IntegerIterator[T]) Same() (value T, hasValue bool) { return i.nextSame(IsSame) }
    80  
    81  // Cancel stops an iteration
    82  //   - after Cancel invocation, Cond, Next and Same indicate no value available
    83  //   - Cancel returns the first error that occurred during iteration, if any
    84  //   - an iterator implementation may require Cancel invocation
    85  //     to release resources
    86  //   - Cancel is deferrable
    87  func (i *IntegerIterator[T]) Cancel(errp ...*error) (err error) {
    88  	if i.isEnd.Load() {
    89  		return // already canceled
    90  	}
    91  	i.isEnd.CompareAndSwap(false, true)
    92  
    93  	return
    94  }
    95  
    96  // nextSame finds the next or the same value. Thread-safe
    97  //   - isSame == IsSame means first or same value should be returned
    98  //   - value is the sought value or the T type’s zero-value if no value exists
    99  //   - hasValue true means value was assigned a valid T value
   100  func (i *IntegerIterator[T]) nextSame(isSame NextAction) (value T, hasValue bool) {
   101  
   102  	// outside lock check
   103  	if i.isEnd.Load() {
   104  		return // no more values return
   105  	}
   106  	i.lock.Lock()
   107  	defer i.lock.Unlock()
   108  
   109  	// inside lock check
   110  	if i.isEnd.Load() {
   111  		return // no more values return
   112  	}
   113  
   114  	// note that first value has been sought
   115  	if !i.didReturnAValue {
   116  		i.didReturnAValue = true
   117  	} else if isSame == IsNext {
   118  		if i.value == i.lastValue {
   119  			if !i.isEnd.Load() {
   120  				i.isEnd.CompareAndSwap(false, true)
   121  			}
   122  			return
   123  		}
   124  		i.value += i.delta
   125  	}
   126  
   127  	value = i.value
   128  	hasValue = true
   129  
   130  	return // value and hasValue valid
   131  }