jackhammer

Utilities for Go
Log | Files | Refs | README | LICENSE

slicesx.go (7970B)


      1 // Package slicesx provides slice utilities that compliment Go standard
      2 // slices package.
      3 package slicesx
      4 
      5 import (
      6 	"errors"
      7 	"fmt"
      8 	"maps"
      9 	"slices"
     10 	"sync"
     11 
     12 	"git.sr.ht/~jackmordaunt/jackhammer/errorsx"
     13 	"git.sr.ht/~jackmordaunt/jackhammer/iterx"
     14 )
     15 
     16 // Fanout across all elements of a slice in parallel.
     17 // The function receives the index and element.
     18 func Fanout[S ~[]E, E any](seq S, fn func(int, E)) {
     19 	iterx.Fanout2(slices.All(seq), fn)
     20 }
     21 
     22 // FanoutErr is like [Fanout] but returns an aggregated error.
     23 func FanoutErr[S ~[]E, E any](seq S, fn func(int, E) error) error {
     24 	return iterx.FanoutErr2(slices.All(seq), fn)
     25 }
     26 
     27 // Map transforms a slice by applying a function to each element and returning the result.
     28 func Map[S ~[]E, E any, Out any](s S, fn func(E) Out) []Out {
     29 	out := make([]Out, len(s))
     30 	for ii := range s {
     31 		out[ii] = fn(s[ii])
     32 	}
     33 	return out
     34 }
     35 
     36 // MapIndex is like [Map] but provides the iteration index to the transform function.
     37 func MapIndex[S ~[]E, E any, Out any](s S, fn func(int, E) Out) []Out {
     38 	out := make([]Out, len(s))
     39 	for ii := range s {
     40 		out[ii] = fn(ii, s[ii])
     41 	}
     42 	return out
     43 }
     44 
     45 // MapSafe is like [Map] but recovers from panics and returns an error instead.
     46 func MapSafe[S ~[]E, E any, Out any](s S, fn func(E) Out) (_ []Out, err error) {
     47 	defer func() {
     48 		if v := recover(); v != nil {
     49 			if e, ok := v.(error); ok {
     50 				err = e
     51 			} else {
     52 				err = fmt.Errorf("%v", v)
     53 			}
     54 		}
     55 	}()
     56 	return Map(s, fn), err
     57 }
     58 
     59 // MapIndexSafe is like [MapIndex] but recovers from panics and returns an error instead.
     60 func MapIndexSafe[S ~[]E, E any, Out any](s S, fn func(int, E) Out) (_ []Out, err error) {
     61 	defer func() {
     62 		if v := recover(); v != nil {
     63 			if e, ok := v.(error); ok {
     64 				err = e
     65 			} else {
     66 				err = fmt.Errorf("%v", v)
     67 			}
     68 		}
     69 	}()
     70 	return MapIndex(s, fn), err
     71 }
     72 
     73 // ParaMap transforms a slice by applying a function to each element and returning the result.
     74 // Transforms are executed concurrently.
     75 func ParaMap[S ~[]E, E any, Out any](s S, fn func(E) Out) []Out {
     76 	out := make([]Out, len(s))
     77 	wg := sync.WaitGroup{}
     78 	for ii := range s {
     79 		wg.Add(1)
     80 		go func() {
     81 			defer wg.Done()
     82 			out[ii] = fn(s[ii])
     83 		}()
     84 	}
     85 	wg.Wait()
     86 	return out
     87 }
     88 
     89 // MapErr transforms a slice by applying a fallible function to each element and
     90 // returning the result.
     91 //
     92 // If the function returns an error the map operation completes, returning an
     93 // aggregated error for each failed transform. The output list will be short
     94 // by the number of failures. Length of the output is not guaranteed to match that
     95 // of the input.
     96 func MapErr[S ~[]E, E any, Out any](s S, fn func(E) (Out, error)) (out []Out, err error) {
     97 	out = make([]Out, 0, len(s))
     98 	for ii := range s {
     99 		v, e := fn(s[ii])
    100 		if e != nil {
    101 			err = errors.Join(err, e)
    102 		} else {
    103 			out = append(out, v)
    104 		}
    105 	}
    106 	return out, err
    107 }
    108 
    109 // ParaMapErr is [MapErr], but all instances of fn run concurrently.
    110 func ParaMapErr[S ~[]E, E any, Out any](s S, fn func(E) (Out, error)) (out []Out, err error) {
    111 	out = make([]Out, len(s))
    112 	errs := make([]error, len(s))
    113 	wg := sync.WaitGroup{}
    114 
    115 	for ii := range s {
    116 		wg.Add(1)
    117 		go func() {
    118 			defer wg.Done()
    119 			v, e := fn(s[ii])
    120 			if e != nil {
    121 				errs[ii] = e
    122 			} else {
    123 				out[ii] = v
    124 			}
    125 		}()
    126 	}
    127 
    128 	wg.Wait()
    129 
    130 	for ii, err := range errs {
    131 		if err != nil {
    132 			out = slices.Delete(out, ii, ii+1)
    133 		}
    134 	}
    135 
    136 	if len(errs) > 0 {
    137 		return out, errorsx.PolyError{Errs: errs}
    138 	}
    139 
    140 	return out, nil
    141 }
    142 
    143 // For processes a sequence with the given function.
    144 func For[S ~[]E, E any](seq S, fn func(int, E)) {
    145 	iterx.For2(slices.All(seq), fn)
    146 }
    147 
    148 // ForErr processes a sequence with the given function.
    149 func ForErr[S ~[]E, E any](seq S, fn func(int, E) error) error {
    150 	return iterx.ForErr2(slices.All(seq), fn)
    151 }
    152 
    153 // Generate returns a slice of [n] items produced by [fn].
    154 func Generate[E any](n int, fn func(int) E) []E {
    155 	out := make([]E, n)
    156 	for ii := range n {
    157 		out[ii] = fn(ii)
    158 	}
    159 	return out
    160 }
    161 
    162 // Repeat returns a slice of [n] items of value [v].
    163 func Repeat[E any](n int, v E) []E {
    164 	out := make([]E, n)
    165 	for ii := range n {
    166 		out[ii] = v
    167 	}
    168 	return out
    169 }
    170 
    171 // PopFront returns the first element from the slice.
    172 func PopFront[S ~[]E, E any](s *S) E {
    173 	defer func() { (*s) = (*s)[1:] }()
    174 	return (*s)[0]
    175 }
    176 
    177 // PopFrontSafe returns the first element from the slice, or false.
    178 func PopFrontSafe[S ~[]E, E any](s *S) (e E, ok bool) {
    179 	if s == nil {
    180 		return e, false
    181 	}
    182 	if len(*s) == 0 {
    183 		return e, false
    184 	}
    185 	return PopFront(s), true
    186 }
    187 
    188 // PopBack returns the last element from the slice.
    189 func PopBack[S ~[]E, E any](s *S) E {
    190 	defer func() { (*s) = (*s)[:len(*s)-1] }()
    191 	return (*s)[len(*s)-1]
    192 }
    193 
    194 // PopBackSafe returns the last element from the slice, or false.
    195 func PopBackSafe[S ~[]E, E any](s *S) (e E, ok bool) {
    196 	if s == nil {
    197 		return e, false
    198 	}
    199 	if len(*s) == 0 {
    200 		return e, false
    201 	}
    202 	return PopBack(s), true
    203 }
    204 
    205 // Filter out elements from a slice when the predicate returns false.
    206 func Filter[S ~[]E, E any](s S, fn func(E) bool) S {
    207 	out := make(S, 0, len(s))
    208 	for _, v := range s {
    209 		if fn(v) {
    210 			out = append(out, v)
    211 		}
    212 	}
    213 	return slices.Clip(out)
    214 }
    215 
    216 // FilterMap maps [In] to [Out], ignoring entries that return false.
    217 func FilterMap[S ~[]In, In any, Out any](s S, fn func(In) (Out, bool)) []Out {
    218 	out := make([]Out, 0, len(s))
    219 	for _, v := range s {
    220 		if v, ok := fn(v); ok {
    221 			out = append(out, v)
    222 		}
    223 	}
    224 	return slices.Clip(out)
    225 }
    226 
    227 // TakeRange selects all items between [start] and [end] and removes them from the slice.
    228 // Does not bounds check.
    229 func TakeRange[S ~[]E, E any](s *S, start, end int) S {
    230 	taken := (*s)[start:end]
    231 	*s = slices.Delete(*s, start, end)
    232 	return taken
    233 }
    234 
    235 // TakeRangeSafe selects all items between [start] and [end] and removes them from the slice.
    236 // Checks the bounds.
    237 func TakeRangeSafe[S ~[]E, E any](s *S, start, end int) (S, bool) {
    238 	if start < 0 || start >= end || end < 0 || end > len(*s)-1 || start > len(*s)-1 {
    239 		return nil, false
    240 	}
    241 	taken := (*s)[start:end]
    242 	*s = slices.Delete(*s, start, end)
    243 	return taken, true
    244 }
    245 
    246 // TakeAllFunc selects all items for which [fn] returns true and removes them from the slice.
    247 func TakeAllFunc[S ~[]E, E any](s *S, fn func(E) bool) (S, bool) {
    248 	indexes := []int{}
    249 	for ii, v := range *s {
    250 		if fn(v) {
    251 			indexes = append(indexes, ii)
    252 		}
    253 	}
    254 	if len(indexes) == 0 {
    255 		return nil, false
    256 	}
    257 	out := make(S, 0, len(indexes))
    258 	for _, index := range indexes {
    259 		out = append(out, (*s)[index])
    260 	}
    261 	*s = slices.DeleteFunc(*s, fn)
    262 	return out, true
    263 }
    264 
    265 // Take selects the item and removes it, without bounds checks.
    266 func Take[S ~[]E, E any](s *S, index int) E {
    267 	taken := (*s)[index]
    268 	*s = slices.Delete(*s, index, index+1)
    269 	return taken
    270 }
    271 
    272 // TakeSafe selects the item and removes it, returning true if found.
    273 func TakeSafe[S ~[]E, E any](s *S, index int) (E, bool) {
    274 	if index < 0 || index > len(*s)-1 {
    275 		return *new(E), false
    276 	}
    277 	taken := (*s)[index]
    278 	*s = slices.Delete(*s, index, index+1)
    279 	return taken, true
    280 }
    281 
    282 // TakeFunc selects the first item that satisfies [fn] and removes it.
    283 func TakeFunc[S ~[]E, E any](s *S, fn func(E) bool) (E, bool) {
    284 	ii := slices.IndexFunc(*s, fn)
    285 	if ii < 0 {
    286 		return *new(E), false
    287 	}
    288 	taken := (*s)[ii]
    289 	*s = slices.Delete(*s, ii, ii+1)
    290 	return taken, true
    291 }
    292 
    293 // Normalize de-duplicates a slices of comparable elements.
    294 // Doesn't rely on sorting.
    295 func Normalize[S ~[]E, E comparable](s S) S {
    296 	s = slices.Clone(s)
    297 	seen := map[E]struct{}{}
    298 	for _, e := range s {
    299 		seen[e] = struct{}{}
    300 	}
    301 	return slices.Collect(maps.Keys(seen))
    302 }
    303 
    304 // Flatten reduces a 2D slices to a 1D slice.
    305 func Flatten[E any](s [][]E) []E {
    306 	count := 0
    307 	for _, ss := range s {
    308 		count += len(ss)
    309 	}
    310 
    311 	out := make([]E, 0, count)
    312 
    313 	for _, ss := range s {
    314 		for _, e := range ss {
    315 			out = append(out, e)
    316 		}
    317 	}
    318 
    319 	return out
    320 }
    321 
    322 // ToMap converts a slice to a map, using [keyFor] to extract the key from each element.
    323 func ToMap[S ~[]E, E any, K comparable](s S, keyFor func(int, E) K) map[K]E {
    324 	return iterx.ToMap(slices.All(s), keyFor)
    325 }