From 20561c574cc7a1f638e34feaf0ad86b7f116bdd3 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sat, 1 Apr 2023 18:49:06 -0700 Subject: [PATCH] sliceutil: helpers for mid-slice insert/delete. Manipulating the hand, deck, etc. is going to use these operations a lot. --- cardsim/deck.go | 15 ++----- cardsim/sliceutil.go | 100 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 cardsim/sliceutil.go diff --git a/cardsim/deck.go b/cardsim/deck.go index d672597..e1fa3a4 100644 --- a/cardsim/deck.go +++ b/cardsim/deck.go @@ -31,8 +31,8 @@ func (d *Deck[C]) Len() int { return len(d.cards) } -// Insert puts a card at a specific location in the Deck. The card previously -// at that location and all locations after are shifted one card later. +// Insert puts one or more cards at a specific location in the Deck. Cards +// at that location and all locations after are shifted deeper into the deck. // Negative indexes are counted from the bottom of the deck. BottomOfDeck is // a sentinel value for the bottommost position; -1 is one card above. // @@ -44,7 +44,7 @@ func (d *Deck[C]) Len() int { // WarningTopClamped. Like all warnings, these can be safely ignored and the // program is in a well-defined state, but you may want to check for them // if you expect some other behavior. -func (d *Deck[C]) Insert(idx int, card Card[C]) error { +func (d *Deck[C]) Insert(idx int, card ...Card[C]) error { var errs ErrorCollector // Calculate actual target index. switch { @@ -60,14 +60,7 @@ func (d *Deck[C]) Insert(idx int, card Card[C]) error { idx += d.Len() } // remaining case: 0 <= idx <= d.Len(), which is a normal forward insert index. - - // Place new card on bottom and "bubble" into position. - // Takes O(N) time. If this turns out to be a problem, implement a more - // efficient data structure. - d.cards = append(d.cards, card) - for i := len(d.cards) - 1; i > idx; i-- { - d.cards[i], d.cards[i-1] = d.cards[i-1], d.cards[i] - } + d.cards = InsertInto(d.cards, idx, card...) return errs.Emit() } diff --git a/cardsim/sliceutil.go b/cardsim/sliceutil.go new file mode 100644 index 0000000..1c3e881 --- /dev/null +++ b/cardsim/sliceutil.go @@ -0,0 +1,100 @@ +package cardsim + +import ( + "fmt" +) + +// 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] +}