github.com/haraldrudell/parl@v0.4.176/iters/slice-pointer.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/perrors"
    13  )
    14  
    15  // SlicePointer traverses a slice container using pointers to value. thread-safe.
    16  //   - the difference is that:
    17  //   - instead of copying a value from the slice,
    18  //   - a pointer to the slice value is returned
    19  type SlicePointer[E any] struct {
    20  	slice []E // the slice providing values
    21  
    22  	// lock serializes Next and Cancel invocations
    23  	lock sync.Mutex
    24  	// didNext indicates that the first value was sought
    25  	//	- behind lock
    26  	didNext bool
    27  	// hasValue indicates that slice[index] is the current value
    28  	//	- behind lock
    29  	hasValue bool
    30  	// index in slice, 0…len(slice)
    31  	//	- behind lock
    32  	index int
    33  	// isEnd indicates no more values available
    34  	//	- written inside lock
    35  	isEnd atomic.Bool
    36  }
    37  
    38  // NewSlicePointerIterator returns an iterator of pointers to T
    39  //   - the difference is that:
    40  //   - instead of copying a value from the slice,
    41  //   - a pointer to the slice value is returned
    42  //   - the returned [Iterator] value cannot be copied, the pointer value
    43  //     must be used
    44  //   - uses self-referencing pointers
    45  func NewSlicePointerIterator[E any](slice []E) (iterator Iterator[*E]) {
    46  	i := SlicePointer[E]{slice: slice}
    47  	return &i
    48  }
    49  
    50  func NewSlicePointerIteratorField[E any](fieldp *SlicePointer[E], slice []E) (iterator Iterator[*E]) {
    51  	fieldp.slice = slice
    52  	iterator = fieldp
    53  	return
    54  }
    55  
    56  // Init implements the right-hand side of a short variable declaration in
    57  // the init statement for a Go “for” clause
    58  //
    59  // Usage:
    60  //
    61  //		for i, iterator := NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    62  //	   // i is pointer to slice element
    63  func (i *SlicePointer[E]) Init() (iterationVariable *E, iterator Iterator[*E]) {
    64  	iterator = i
    65  	return
    66  }
    67  
    68  // Cond implements the condition statement of a Go “for” clause
    69  //   - the iterationVariable is updated by being provided as a pointer.
    70  //     iterationVariable cannot be nil
    71  //   - errp is an optional error pointer receiving any errors during iterator execution
    72  //   - condition is true if iterationVariable was assigned a value and the iteration should continue
    73  //
    74  // Usage:
    75  //
    76  //		for i, iterator := NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    77  //	   // i is pointer to slice element
    78  func (i *SlicePointer[E]) Cond(iterationVariablep **E, errp ...*error) (condition bool) {
    79  	if iterationVariablep == nil {
    80  		perrors.NewPF("iterationVariablep cannot bee nil")
    81  	}
    82  
    83  	// check for next value
    84  	var value *E
    85  	if value, condition = i.nextSame(IsNext); condition {
    86  		*iterationVariablep = value
    87  	}
    88  
    89  	return // condition and iterationVariablep updated, errp unchanged
    90  }
    91  
    92  // Next advances to next item and returns it
    93  //   - if hasValue true, value contains the next value
    94  //   - otherwise, no more items exist and value is the data type zero-value
    95  func (i *SlicePointer[T]) Next() (value *T, hasValue bool) { return i.nextSame(IsNext) }
    96  
    97  // Same returns the same value again
    98  //   - if hasValue true, value is valid
    99  //   - otherwise, no more items exist and value is the data type zero-value
   100  //   - If Next or Cond has not been invoked, Same first advances to the first item
   101  func (i *SlicePointer[T]) Same() (value *T, hasValue bool) { return i.nextSame(IsSame) }
   102  
   103  // Cancel release resources for this iterator. Thread-safe
   104  //   - not every iterator requires a Cancel invocation
   105  func (i *SlicePointer[E]) Cancel(errp ...*error) (err error) {
   106  	i.isEnd.CompareAndSwap(false, true)
   107  	return
   108  }
   109  
   110  // Next finds the next or the same value. Thread-safe
   111  //   - isSame true means first or same value should be returned
   112  //   - value is the sought value or the T type’s zero-value if no value exists
   113  //   - hasValue true means value was assigned a valid T value
   114  func (i *SlicePointer[E]) nextSame(isSame NextAction) (value *E, hasValue bool) {
   115  
   116  	if i.isEnd.Load() {
   117  		return // no more values return
   118  	}
   119  
   120  	i.lock.Lock()
   121  	defer i.lock.Unlock()
   122  
   123  	if i.isEnd.Load() {
   124  		return // no more values return
   125  	}
   126  
   127  	// for IsSame operation the first value must be sought
   128  	//	- therefore, if the first value has not been sought, seek it now or
   129  	//	- if not IsSame operation, advance to the next value
   130  	if !i.didNext || isSame != IsSame {
   131  
   132  		// note that first value has been sought
   133  		if !i.didNext {
   134  			i.didNext = true
   135  		}
   136  
   137  		// find slice index to use
   138  		//	- if a value was found, advance index
   139  		//	- final i.index value is len(i.slice)
   140  		if i.hasValue {
   141  			i.index++
   142  		}
   143  
   144  		// check if the new index is within available slice values
   145  		//	- when i.index has reached len(i.slice), i.hasValue is always false
   146  		//	- when hasValue is false, i.index will no longer be incremented
   147  		i.hasValue = i.index < len(i.slice)
   148  	}
   149  
   150  	// update hasValue and value
   151  	//	- get the value if it is valid, otherwise zero-value
   152  	if hasValue = i.hasValue; hasValue {
   153  		value = &i.slice[i.index]
   154  	} else {
   155  		i.isEnd.CompareAndSwap(false, true)
   156  	}
   157  
   158  	return // value and hasValue indicates availability
   159  }