100 lines
3.3 KiB
Go
100 lines
3.3 KiB
Go
package stack
|
|
|
|
type Stack struct {
|
|
items []Keyer
|
|
}
|
|
|
|
// things that are to be in the stack have to implement Key() string
|
|
type Keyer interface {
|
|
Key() string
|
|
}
|
|
|
|
func NewStack(size int) *Stack {
|
|
s := Stack{items: make([]Keyer, 0, size)}
|
|
return &s
|
|
}
|
|
|
|
// TODO (2026-10-16): look up how to make a proper enumerable / iterable array in golang
|
|
// it is apparently relatively recent (2024): https://go.dev/blog/range-functions
|
|
func (s* Stack) Items() []Keyer {
|
|
return s.items
|
|
}
|
|
|
|
func (s* Stack) Size() int {
|
|
return len(s.items)
|
|
}
|
|
|
|
type emptyKeyer struct{}
|
|
func (e *emptyKeyer) Key() string { return "" }
|
|
|
|
func IsEmpty(k Keyer) bool {
|
|
return k.Key() == ""
|
|
}
|
|
|
|
// NOTE (2026-10-30): i actually don't have a concrete-enough use case for PushAndReplace, it is more a nice to have. will pause it for now
|
|
// func PushAndReplace(s *stack, newItem *Keyer) {
|
|
// itemOnStack, _, _ := s.Push(newItem)
|
|
// TODO (2026-10-30): how to correctly make it so that pointer "itemOnStack" reflects the values inherent in newItem)
|
|
// basically want newItem to be "force pushed" onto stack instead of maintaining the previous item that matched
|
|
// newItem.Key()
|
|
// }
|
|
|
|
// attempt to push newItem on stack. maintains the pointer of the first instance of item being pushed on stack
|
|
//
|
|
// if newItem WAS on the stack
|
|
// * bumps value that was already on stack to top of stack
|
|
// * return the value that was already in the stack and false (no eviction)
|
|
//
|
|
// if newItem WAS NOT on the stack:
|
|
// * put newItem at top of stack, and..
|
|
// * returns newItem and false (no eviction) if stack was below capacity
|
|
// * returns evicted item and true (eviction) if stack was above capacity
|
|
// NOTE (2026-10-29): bit complex tho init?
|
|
const ITEM_EVICTED = true
|
|
// NOTE (2026-10-30): could pursue a signature like the following instead
|
|
// Push(newItem keyer) (itemOnStack, itemEvicted, booleanEvictedItem)
|
|
//
|
|
// item, evictedItem, evicted := exampleStack.Push(anItem)
|
|
// item := exampleStack.Push(anItem)
|
|
// _, evictedItem, evicted := exampleStack.Push(anItem)
|
|
func (s *Stack) Push (newItem Keyer) (Keyer, bool, Keyer) {
|
|
// stack below capacity, and newItem was at front of stack - exit early
|
|
if len(s.items) > 0 && s.items[0].Key() == newItem.Key() {
|
|
return s.items[0], !ITEM_EVICTED, &emptyKeyer{}
|
|
}
|
|
prev := newItem
|
|
for i, elem := range s.items {
|
|
// insert newItem at front
|
|
if i == 0 {
|
|
prev = s.items[0]
|
|
s.items[0] = newItem
|
|
// newItem was already in stack (therefore we will not evict any items!)
|
|
// do in-place swap and exit early
|
|
} else if elem.Key() == newItem.Key() {
|
|
s.items[i] = prev
|
|
// ignore value of newItem and re-use what was already in stack instead
|
|
s.items[0] = elem
|
|
// return the previously stored value / pointer
|
|
return elem, !ITEM_EVICTED, &emptyKeyer{}
|
|
// propagate change outwards in stack, moving all items one step further
|
|
} else {
|
|
temp := s.items[i]
|
|
s.items[i] = prev
|
|
prev = temp
|
|
}
|
|
}
|
|
|
|
/* newItem wasn't in stack */
|
|
// below we might need to evict an item already on the stack, depending on the current capacity
|
|
|
|
// if we still have capacity, append the last item back onto the end of the stack
|
|
if len(s.items) < cap(s.items) {
|
|
s.items = append(s.items, prev)
|
|
} else {
|
|
// we evict the list item, forgetting about it and return the evicted item
|
|
return newItem, ITEM_EVICTED, prev
|
|
}
|
|
return newItem, !ITEM_EVICTED, &emptyKeyer{}
|
|
}
|
|
|