package cardsim import ( "fmt" "math/rand" ) // InsertInto inserts one or more items into a slice at an arbitrary index. // Items already in the slice past the target position move to later positons. // // Like `append`, this may move the underlying array and it produces a new // slice header (under the hood, it uses `append`). It returns the new slice // (the original is in an undefined state and should no longer be used). // // If loc is negative or more than one past the end of T, Insert panics. func InsertInto[T any](slice []T, loc int, elements ...T) []T { if loc < 0 || loc > len(slice) { panic(fmt.Sprintf("can't Insert at location %d in %d-element slice", loc, len(slice))) } // is this a no-op? if len(elements) == 0 { return slice } // is this just an append? if loc == len(slice) { return append(slice, elements...) } offset := len(elements) oldLen := len(slice) newSize := oldLen + offset if newSize <= cap(slice) { // We can reslice in place. slice = slice[:newSize] // Scoot trailing to their new positions. copy(slice[loc+offset:], slice[loc:oldLen]) // Insert the new elements. copy(slice[loc:], elements) return slice } // Reallocate. Do the normal thing of doubling the size as a minimum // when increasing space for a dynamic array; this amortizes the // cost of repeatedly reallocating and moving the slice. newCap := cap(slice) * 2 if newCap < newSize { newCap = newSize } newSlice := make([]T, newSize, newCap) if loc > 0 { copy(newSlice, slice[0:loc]) } copy(newSlice[loc:], elements) copy(newSlice[loc+offset:], slice[loc:]) return newSlice } // DeleteFrom deletes an item from a slice at an arbitrary index. Items after it // scoot up to close the gap. This returns the modified slice (like Append). // // If the provided location is not a valid location in the slice, this panics. func DeleteFrom[T any](slice []T, loc int) []T { return DeleteNFrom(slice, loc, 1) } // DeleteNFrom deletes N items from a slice at an arbitrary index. Items after // it scoot up to close the gap. This returns the modified slice (like Append). // // If the range of items that would be deleted is not entirely valid within the // slice, this panics. func DeleteNFrom[T any](slice []T, loc, n int) []T { if loc < 0 || loc+n > len(slice) { panic(fmt.Sprintf("can't delete %d elements from a %d-element slice at location %d", n, len(slice), loc)) } // Easy cases. if n == 0 { return slice } if loc == 0 { return slice[n:] } if loc+n == len(slice) { return slice[0:loc] } // Is it shorter to move up or move down? if len(slice)-loc-n > loc { // Move forward -- the end is big. copy(slice[n:], slice[:loc]) return slice[n:] } // Move backward -- the beginnng is big or they're the same size // (and moving backwards preserves more usable append capacity later). copy(slice[loc:], slice[loc+n:]) return slice[:len(slice)-n] } // ShuffleAll shuffles everything in slice, using the provided rand.Rand. // If no rand.Rand is provided, this uses the default source. func ShuffleAll[T any](slice []T, r *rand.Rand) { shuffle := rand.Shuffle if r != nil { shuffle = r.Shuffle } shuffle(len(slice), func(i, j int) { slice[i], slice[j] = slice[j], slice[i] }) } // ShufflePart shuffles the `n` elements of `slice` starting at `loc` // in-place, using the provided rand.Rand. If the range of items to // shuffle is not entirely within `slice`, this panics. // // If no rand.Rand is provided, this uses the default source. func ShufflePart[T any](slice []T, r *rand.Rand, loc, n int) { if loc < 0 || loc+n > len(slice) { panic(fmt.Sprintf("can't shuffle %d elements from a %d-element slice at location %d", n, len(slice), loc)) } if n < 1 { return } ShuffleAll(slice[loc:loc+n], r) } // Strip iterates T, removing any element for which removeWhen returns true // (when provided the index of the element and the element itself as arguments). // It returns the stripped slice. func Strip[T any](slice []T, removeWhen func(idx int, t T) bool) []T { if len(slice) == 0 { return nil } to := 0 for from, e := range slice { if !removeWhen(from, e) { if to != from { slice[to] = slice[from] } to++ } } return slice[:to] } // EnsureCapacity checks if `cap(slice)` is at least req. If so, it returns // slice unchanged. Otherwise, it copies `slice` to a new slice that is at least // capacity `req` (but may be larger) and returns the copy. // // It is reasonably efficient to use EnsureCapacity consecutively without // regard for the final overall capacity that a specific slice will need to be. func EnsureCapacity[T any](slice []T, req int) []T { if cap(slice) >= req { return slice } if req < 2*cap(slice) { req = 2 * cap(slice) } ret := make([]T, len(slice), req) copy(ret, slice) return ret }