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 }