Compare commits
10 Commits
4eb50dd335
...
5475d0636f
| Author | SHA1 | Date | |
|---|---|---|---|
| 5475d0636f | |||
| 8dc9f22189 | |||
| 1434ccf52b | |||
| d2dcd223a6 | |||
| 1a93503112 | |||
| f53776ede0 | |||
| 77a6e0fa54 | |||
| 95e39c652e | |||
| b5321f51ce | |||
| 839457702f |
@@ -1,20 +1,46 @@
|
|||||||
# DS
|
# Data Structures & Algorithms in Go
|
||||||
|
|
||||||
## Linear
|
## Linear
|
||||||
|
|
||||||
- [x] Stack
|
- [x] Stack
|
||||||
- [x] Queue
|
- [x] Queue
|
||||||
- [ ] Linked List
|
- [x] Double Linked List
|
||||||
- [ ] Circular Buffer
|
- [x] Circular Buffer
|
||||||
- [ ] Deque
|
- [ ] Deque (segmented array), ⛔ Not possible in Go
|
||||||
|
|
||||||
## Graph
|
## Tree — hierarchical, parent/child relationships
|
||||||
|
|
||||||
## Tree
|
- [ ] Binary Tree
|
||||||
|
- [ ] Binary Search Tree
|
||||||
|
- [ ] AVL Tree
|
||||||
|
- [ ] Heap (min/max)
|
||||||
|
- [ ] Trie
|
||||||
|
|
||||||
|
## Graph — nodes connected by edges, no strict hierarchy
|
||||||
|
|
||||||
|
- [ ] Directed
|
||||||
|
- [ ] Undirected
|
||||||
|
- [ ] Weighted
|
||||||
|
|
||||||
|
## Hash Based — key/value
|
||||||
|
|
||||||
|
- [ ] Hash Map
|
||||||
|
- [ ] Hash Set
|
||||||
|
|
||||||
|
# Algorithms ω
|
||||||
|
|
||||||
|
- [x] Kadane's Algorithm
|
||||||
|
|
||||||
|
# Each category solves different problems:
|
||||||
|
|
||||||
|
- Linear — ordered data, undo/redo, scheduling
|
||||||
|
- Tree — searching, sorting, hierarchical data like file systems
|
||||||
|
- Graph — networks, maps, social connections, dependencies
|
||||||
|
- Hash — fast lookups, caching, counting
|
||||||
|
- Set — membership testing, deduplication
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go doc -all ./linear
|
go doc -all ./linear | bat -l go
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package algo
|
||||||
|
|
||||||
|
// kadane's algorithm
|
||||||
|
// https://www.youtube.com/watch?v=qj3CjNEKFeM
|
||||||
|
|
||||||
|
// MaxSubarray -> returns the max sum of possible sub arrays
|
||||||
|
func MaxSubarray(array []int) int {
|
||||||
|
minimum := array[0]
|
||||||
|
maximum := 0
|
||||||
|
|
||||||
|
for i := 1; i < len(array); i++ {
|
||||||
|
if array[i] < minimum {
|
||||||
|
minimum = array[i]
|
||||||
|
} else if array[i]-minimum > maximum {
|
||||||
|
maximum = array[i] - minimum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maximum
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetChunkSize() -> returns the amount of elements for chunk or 1
|
||||||
|
// if size of T > 521, (unused)
|
||||||
|
// c++ 512 bytes implementation
|
||||||
|
func GetChunkSize[T any](val T) uintptr {
|
||||||
|
return max(512/unsafe.Sizeof(*new(T)), 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package linear
|
||||||
|
|
||||||
|
type baseTypes interface {
|
||||||
|
int | int8 | int16 | int32 | int64 |
|
||||||
|
uint | uint8 | uint16 | uint32 | uint64 |
|
||||||
|
float32 | float64 | string | bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CircularBuffer -> Circular Buffer struct only takes baseTypes
|
||||||
|
type CircularBuffer[T baseTypes] struct {
|
||||||
|
array [10]T
|
||||||
|
cHead int
|
||||||
|
cTail int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add() -> adds values wrapping around to overwrite oldest
|
||||||
|
func (cb *CircularBuffer[T]) Add(val T) {
|
||||||
|
cb.array[cb.cTail] = val
|
||||||
|
|
||||||
|
cb.cTail = (cb.cTail + 1) % len(cb.array) // wrap around
|
||||||
|
if cb.size < len(cb.array) {
|
||||||
|
cb.size++
|
||||||
|
} else {
|
||||||
|
cb.cHead = (cb.cHead + 1) % len(cb.array) // overwrite oldest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data() -> returns the array in CircularBuffer
|
||||||
|
func (cb CircularBuffer[T]) Data() [10]T {
|
||||||
|
return cb.array
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package linear
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node[T any] struct {
|
||||||
|
data T
|
||||||
|
next *Node[T]
|
||||||
|
prev *Node[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkedList[T any] struct {
|
||||||
|
head *Node[T]
|
||||||
|
tail *Node[T]
|
||||||
|
length uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinkedList() -> Creates a linked list NewLinkedList[Type]()
|
||||||
|
func NewLinkedList[T any]() *LinkedList[T] {
|
||||||
|
return &LinkedList[T]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertAtHead() -> inserts data and sets it as head
|
||||||
|
func (ll *LinkedList[T]) InsertAtHead(data T) {
|
||||||
|
newNode := &Node[T]{data: data}
|
||||||
|
|
||||||
|
if ll.head == nil {
|
||||||
|
|
||||||
|
ll.head = newNode
|
||||||
|
ll.tail = newNode
|
||||||
|
} else {
|
||||||
|
newNode.next = ll.head
|
||||||
|
ll.head.prev = newNode
|
||||||
|
ll.head = newNode
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.length += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertAtTail() -> inserts node at tail
|
||||||
|
func (ll *LinkedList[T]) InsertAtTail(data T) {
|
||||||
|
newNode := &Node[T]{data: data}
|
||||||
|
|
||||||
|
if ll.tail == nil {
|
||||||
|
|
||||||
|
ll.head = newNode
|
||||||
|
ll.tail = newNode
|
||||||
|
} else {
|
||||||
|
newNode.prev = ll.tail
|
||||||
|
ll.tail.next = newNode
|
||||||
|
ll.tail = newNode
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.length += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintList() -> prints linked list head to tail
|
||||||
|
func (ll *LinkedList[T]) PrintList() {
|
||||||
|
current := ll.head
|
||||||
|
|
||||||
|
for current != nil {
|
||||||
|
|
||||||
|
if current.prev != nil {
|
||||||
|
fmt.Print(" <--> ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(current.data)
|
||||||
|
current = current.next
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data() -> returns a pointer to head.data
|
||||||
|
func (ll LinkedList[T]) Data() *T {
|
||||||
|
if ll.head == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ll.head.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHead() -> deletes front node if there is a node to delete
|
||||||
|
func (ll *LinkedList[T]) DeleteHead() {
|
||||||
|
if ll.head != nil {
|
||||||
|
if ll.head == ll.tail {
|
||||||
|
ll.head = nil
|
||||||
|
ll.tail = nil
|
||||||
|
} else {
|
||||||
|
ll.head = ll.head.next
|
||||||
|
ll.head.prev = nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.length -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTail() -> removes end node
|
||||||
|
func (ll *LinkedList[T]) DeleteTail() {
|
||||||
|
if ll.tail != nil {
|
||||||
|
if ll.head == ll.tail {
|
||||||
|
ll.head = nil
|
||||||
|
ll.tail = nil
|
||||||
|
} else {
|
||||||
|
ll.tail = ll.tail.prev
|
||||||
|
ll.tail.next = nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.length -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-12
@@ -6,26 +6,26 @@ import (
|
|||||||
|
|
||||||
// Stack -> creates a Stack with no size
|
// Stack -> creates a Stack with no size
|
||||||
type Stack[T any] struct {
|
type Stack[T any] struct {
|
||||||
Capacity int
|
capacity int
|
||||||
Container []T
|
container []T
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackFixed -> creates Stack with a fixed size
|
// StackFixed -> creates Stack with a fixed size
|
||||||
func StackFixed[T any](value int) *Stack[T] {
|
func StackFixed[T any](value int) *Stack[T] {
|
||||||
return &Stack[T]{
|
return &Stack[T]{
|
||||||
Capacity: value,
|
capacity: value,
|
||||||
Container: make([]T, 0, value),
|
container: make([]T, 0, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push -> Appends value to stack returns error
|
// Push -> Appends value to stack returns error
|
||||||
// If using StackFixed errors when over capacity
|
// If using StackFixed errors when over capacity
|
||||||
func (s *Stack[T]) Push(value T) error {
|
func (s *Stack[T]) Push(value T) error {
|
||||||
if s.Capacity > 0 && (len(s.Container) >= s.Capacity) {
|
if s.capacity > 0 && (len(s.container) >= s.capacity) {
|
||||||
return errors.New("Error max capasity exeded")
|
return errors.New("Error max capasity exeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Container = append(s.Container, value)
|
s.container = append(s.container, value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,27 +33,32 @@ func (s *Stack[T]) Push(value T) error {
|
|||||||
func (s *Stack[T]) Pop() (T, error) {
|
func (s *Stack[T]) Pop() (T, error) {
|
||||||
var zero T
|
var zero T
|
||||||
|
|
||||||
if len(s.Container) <= 0 {
|
if len(s.container) <= 0 {
|
||||||
return zero, errors.New("Error Empty Stack")
|
return zero, errors.New("Error Empty Stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
last := s.Container[len(s.Container)-1]
|
last := s.container[len(s.container)-1]
|
||||||
s.Container = s.Container[:len(s.Container)-1]
|
s.container = s.container[:len(s.container)-1]
|
||||||
|
|
||||||
return last, nil
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear -> clears the stack
|
// Clear -> clears the stack
|
||||||
func (s *Stack[T]) Clear() {
|
func (s *Stack[T]) Clear() {
|
||||||
s.Container = []T{}
|
s.container = []T{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek -> returns top item
|
// Peek -> returns top item
|
||||||
func (s *Stack[T]) Peek() T {
|
func (s *Stack[T]) Peek() T {
|
||||||
return s.Container[len(s.Container)-1]
|
return s.container[len(s.container)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// First -> returns bottom item
|
// First -> returns bottom item
|
||||||
func (s *Stack[T]) First() T {
|
func (s *Stack[T]) First() T {
|
||||||
return s.Container[0]
|
return s.container[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size -> returns number of items in stack
|
||||||
|
func (s *Stack[T]) Size() int {
|
||||||
|
return len(s.container)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package linear
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPush(t *testing.T) {
|
|
||||||
s := Stack[int]{}
|
|
||||||
s.Push(1)
|
|
||||||
s.Push(2)
|
|
||||||
s.Push(3)
|
|
||||||
|
|
||||||
if len(s.Container) != 3 {
|
|
||||||
t.Errorf("expected 2 items, got %d", len(s.Container))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/linear"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- helpers ---
|
||||||
|
|
||||||
|
func intBuffer(vals ...int) linear.CircularBuffer[int] {
|
||||||
|
var cb linear.CircularBuffer[int]
|
||||||
|
for _, v := range vals {
|
||||||
|
cb.Add(v)
|
||||||
|
}
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- empty buffer ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_EmptyData(t *testing.T) {
|
||||||
|
var cb linear.CircularBuffer[int]
|
||||||
|
data := cb.Data()
|
||||||
|
for i, v := range data {
|
||||||
|
if v != 0 {
|
||||||
|
t.Errorf("expected zero at index %d, got %d", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- basic add ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_AddOne(t *testing.T) {
|
||||||
|
cb := intBuffer(42)
|
||||||
|
if cb.Data()[0] != 42 {
|
||||||
|
t.Errorf("expected 42 at index 0, got %d", cb.Data()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCircularBuffer_AddMultiple(t *testing.T) {
|
||||||
|
cb := intBuffer(1, 2, 3, 4, 5)
|
||||||
|
data := cb.Data()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if data[i] != i+1 {
|
||||||
|
t.Errorf("index %d: expected %d, got %d", i, i+1, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- fill to exact capacity (10) ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_FillExact(t *testing.T) {
|
||||||
|
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||||
|
data := cb.Data()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if data[i] != i+1 {
|
||||||
|
t.Errorf("index %d: expected %d, got %d", i, i+1, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- overflow: oldest value must be overwritten ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_OverflowByOne(t *testing.T) {
|
||||||
|
// fill with 1..10 then add 11; slot 0 gets overwritten
|
||||||
|
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
|
||||||
|
data := cb.Data()
|
||||||
|
if data[0] != 11 {
|
||||||
|
t.Errorf("expected slot 0 to be overwritten with 11, got %d", data[0])
|
||||||
|
}
|
||||||
|
// slots 1-9 should still hold 2-10
|
||||||
|
for i := 1; i < 10; i++ {
|
||||||
|
if data[i] != i+1 {
|
||||||
|
t.Errorf("slot %d: expected %d, got %d", i, i+1, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCircularBuffer_OverflowBy5(t *testing.T) {
|
||||||
|
// add 15 values: last 10 should be 6..15
|
||||||
|
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
|
||||||
|
data := cb.Data()
|
||||||
|
// slots 0-4 are overwritten with 11-15
|
||||||
|
expected := [10]int{11, 12, 13, 14, 15, 6, 7, 8, 9, 10}
|
||||||
|
for i, want := range expected {
|
||||||
|
if data[i] != want {
|
||||||
|
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- double wrap: add exactly 2x capacity ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_DoubleWrap(t *testing.T) {
|
||||||
|
// add 20 values; all original slots fully replaced by 11-20
|
||||||
|
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
|
||||||
|
data := cb.Data()
|
||||||
|
expected := [10]int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||||
|
for i, want := range expected {
|
||||||
|
if data[i] != want {
|
||||||
|
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- stress: 100 additions ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_StressAdd(t *testing.T) {
|
||||||
|
var cb linear.CircularBuffer[int]
|
||||||
|
for i := 1; i <= 100; i++ {
|
||||||
|
cb.Add(i)
|
||||||
|
}
|
||||||
|
// last 10 values added were 91-100
|
||||||
|
// after 100 adds: tail wraps, slots should hold 91-100
|
||||||
|
data := cb.Data()
|
||||||
|
// slot index = (i-1) % 10, value = i for i in 91..100
|
||||||
|
expected := [10]int{91, 92, 93, 94, 95, 96, 97, 98, 99, 100}
|
||||||
|
for i, want := range expected {
|
||||||
|
if data[i] != want {
|
||||||
|
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- string type ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_StringType(t *testing.T) {
|
||||||
|
var cb linear.CircularBuffer[string]
|
||||||
|
cb.Add("hello")
|
||||||
|
cb.Add("world")
|
||||||
|
data := cb.Data()
|
||||||
|
if data[0] != "hello" {
|
||||||
|
t.Errorf("expected hello, got %s", data[0])
|
||||||
|
}
|
||||||
|
if data[1] != "world" {
|
||||||
|
t.Errorf("expected world, got %s", data[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCircularBuffer_StringOverflow(t *testing.T) {
|
||||||
|
var cb linear.CircularBuffer[string]
|
||||||
|
words := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"}
|
||||||
|
for _, w := range words {
|
||||||
|
cb.Add(w)
|
||||||
|
}
|
||||||
|
// "k" overwrote slot 0 ("a")
|
||||||
|
if cb.Data()[0] != "k" {
|
||||||
|
t.Errorf("expected 'k' at slot 0, got '%s'", cb.Data()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- float type ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_FloatType(t *testing.T) {
|
||||||
|
var cb linear.CircularBuffer[float64]
|
||||||
|
cb.Add(3.14)
|
||||||
|
cb.Add(2.71)
|
||||||
|
data := cb.Data()
|
||||||
|
if data[0] != 3.14 {
|
||||||
|
t.Errorf("expected 3.14, got %f", data[0])
|
||||||
|
}
|
||||||
|
if data[1] != 2.71 {
|
||||||
|
t.Errorf("expected 2.71, got %f", data[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- idempotent Data() call ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_DataDoesNotMutate(t *testing.T) {
|
||||||
|
cb := intBuffer(1, 2, 3)
|
||||||
|
first := cb.Data()
|
||||||
|
second := cb.Data()
|
||||||
|
if first != second {
|
||||||
|
t.Error("consecutive Data() calls returned different results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- zero value after overflow stays zero for untouched slots ---
|
||||||
|
|
||||||
|
func TestCircularBuffer_UnusedSlotsAreZero(t *testing.T) {
|
||||||
|
cb := intBuffer(7) // only slot 0 written
|
||||||
|
data := cb.Data()
|
||||||
|
for i := 1; i < 10; i++ {
|
||||||
|
if data[i] != 0 {
|
||||||
|
t.Errorf("slot %d: expected 0, got %d", i, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/linear"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- InsertAtHead ---
|
||||||
|
|
||||||
|
func TestLinkedListInsertAtHeadSingle(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(42)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil {
|
||||||
|
t.Fatal("expected non-nil Data after InsertAtHead, got nil")
|
||||||
|
}
|
||||||
|
if *got != 42 {
|
||||||
|
t.Errorf("expected 42, got %d", *got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListInsertAtHeadOrderIsLIFO(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(1)
|
||||||
|
ll.InsertAtHead(2)
|
||||||
|
ll.InsertAtHead(3)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 3 {
|
||||||
|
t.Errorf("expected 3 (last inserted) at head, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- InsertAtTail ---
|
||||||
|
|
||||||
|
func TestLinkedListInsertAtTailSingle(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[string]{}
|
||||||
|
ll.InsertAtTail("hello")
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != "hello" {
|
||||||
|
t.Errorf("expected 'hello' at head, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListInsertAtTailDoesNotMoveHead(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(1)
|
||||||
|
ll.InsertAtTail(2)
|
||||||
|
ll.InsertAtTail(3)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 1 {
|
||||||
|
t.Errorf("expected head to remain 1 after tail inserts, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Data ---
|
||||||
|
|
||||||
|
func TestLinkedListDataOnEmpty(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected nil Data on empty list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DeleteHead ---
|
||||||
|
|
||||||
|
func TestLinkedListDeleteHeadOnEmpty(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.DeleteHead() // must not panic
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected nil Data after DeleteHead on empty list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListDeleteHeadSingleElement(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(5)
|
||||||
|
ll.DeleteHead()
|
||||||
|
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected nil after deleting the only element via DeleteHead")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListDeleteHeadAdvancesHead(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(1)
|
||||||
|
ll.InsertAtHead(2)
|
||||||
|
ll.InsertAtHead(3)
|
||||||
|
|
||||||
|
ll.DeleteHead()
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 2 {
|
||||||
|
t.Errorf("expected 2 after deleting head (3), got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DeleteTail ---
|
||||||
|
|
||||||
|
func TestLinkedListDeleteTailOnEmpty(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.DeleteTail() // must not panic
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected nil Data after DeleteTail on empty list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListDeleteTailSingleElement(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtTail(10)
|
||||||
|
ll.DeleteTail()
|
||||||
|
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected nil after deleting the only element via DeleteTail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListDeleteTailRemovesLastElement(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtTail(1)
|
||||||
|
ll.InsertAtTail(2)
|
||||||
|
ll.InsertAtTail(3)
|
||||||
|
|
||||||
|
ll.DeleteTail()
|
||||||
|
|
||||||
|
// walk head → next to verify 3 is gone
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 1 {
|
||||||
|
t.Errorf("expected head to remain 1, got %v", got)
|
||||||
|
}
|
||||||
|
ll.DeleteHead()
|
||||||
|
got = ll.Data()
|
||||||
|
if got == nil || *got != 2 {
|
||||||
|
t.Errorf("expected second element to be 2 (3 was deleted), got %v", got)
|
||||||
|
}
|
||||||
|
ll.DeleteHead()
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected empty list after removing all elements")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- string type ---
|
||||||
|
|
||||||
|
func TestLinkedListWithStrings(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[string]{}
|
||||||
|
ll.InsertAtHead("bob")
|
||||||
|
ll.InsertAtHead("alice")
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != "alice" {
|
||||||
|
t.Errorf("expected alice at head, got %v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.DeleteHead()
|
||||||
|
got = ll.Data()
|
||||||
|
if got == nil || *got != "bob" {
|
||||||
|
t.Errorf("expected bob after deleting alice, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListZeroValue(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(0)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 0 {
|
||||||
|
t.Errorf("expected zero-value int at head, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pointer type ---
|
||||||
|
|
||||||
|
func TestLinkedListWithPointers(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[*int]{}
|
||||||
|
v1, v2 := 10, 20
|
||||||
|
p1, p2 := &v1, &v2
|
||||||
|
|
||||||
|
ll.InsertAtTail(p1)
|
||||||
|
ll.InsertAtTail(p2)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != p1 {
|
||||||
|
t.Errorf("expected p1 at head, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListPointerMutation(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[*int]{}
|
||||||
|
v := 42
|
||||||
|
p := &v
|
||||||
|
ll.InsertAtHead(p)
|
||||||
|
|
||||||
|
v = 99
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || **got != 99 {
|
||||||
|
t.Errorf("expected mutation reflected via pointer, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListNilPointer(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[*int]{}
|
||||||
|
ll.InsertAtHead(nil)
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil {
|
||||||
|
t.Fatal("expected non-nil Data pointer (pointing to a nil *int), got nil")
|
||||||
|
}
|
||||||
|
if *got != nil {
|
||||||
|
t.Errorf("expected stored nil *int, got %v", *got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- mixed operations ---
|
||||||
|
|
||||||
|
func TestLinkedListAlternatingInsertDelete(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[int]{}
|
||||||
|
ll.InsertAtHead(1)
|
||||||
|
ll.InsertAtTail(2)
|
||||||
|
ll.InsertAtHead(0)
|
||||||
|
// list: 0 <--> 1 <--> 2
|
||||||
|
|
||||||
|
ll.DeleteTail()
|
||||||
|
// list: 0 <--> 1
|
||||||
|
|
||||||
|
got := ll.Data()
|
||||||
|
if got == nil || *got != 0 {
|
||||||
|
t.Errorf("expected 0 at head, got %v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.DeleteHead()
|
||||||
|
// list: 1
|
||||||
|
got = ll.Data()
|
||||||
|
if got == nil || *got != 1 {
|
||||||
|
t.Errorf("expected 1 as sole remaining element, got %v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.DeleteHead()
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected empty list after deleting all elements")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkedListDeleteBothEndsToEmpty(t *testing.T) {
|
||||||
|
ll := &linear.LinkedList[string]{}
|
||||||
|
ll.InsertAtTail("a")
|
||||||
|
ll.InsertAtTail("b")
|
||||||
|
|
||||||
|
ll.DeleteHead()
|
||||||
|
ll.DeleteTail()
|
||||||
|
|
||||||
|
if ll.Data() != nil {
|
||||||
|
t.Error("expected empty list after removing both elements")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/algo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- basic profit cases ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_BasicProfit(t *testing.T) {
|
||||||
|
// buy at 1, sell at 6 → 5
|
||||||
|
got := algo.MaxSubarray([]int{7, 1, 5, 3, 6, 4})
|
||||||
|
if got != 5 {
|
||||||
|
t.Errorf("expected 5, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxSubarray_ProfitAtEnd(t *testing.T) {
|
||||||
|
// buy at 1, sell at 9 → 8
|
||||||
|
got := algo.MaxSubarray([]int{3, 1, 4, 1, 5, 9})
|
||||||
|
if got != 8 {
|
||||||
|
t.Errorf("expected 8, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxSubarray_AlreadySorted(t *testing.T) {
|
||||||
|
// buy at 1, sell at 5 → 4
|
||||||
|
got := algo.MaxSubarray([]int{1, 2, 3, 4, 5})
|
||||||
|
if got != 4 {
|
||||||
|
t.Errorf("expected 4, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- no profit cases ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_StrictlyDecreasing(t *testing.T) {
|
||||||
|
// prices only fall; no profitable trade
|
||||||
|
got := algo.MaxSubarray([]int{5, 4, 3, 2, 1})
|
||||||
|
if got != 0 {
|
||||||
|
t.Errorf("expected 0, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxSubarray_AllSame(t *testing.T) {
|
||||||
|
got := algo.MaxSubarray([]int{3, 3, 3, 3})
|
||||||
|
if got != 0 {
|
||||||
|
t.Errorf("expected 0, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- single element ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_SingleElement(t *testing.T) {
|
||||||
|
got := algo.MaxSubarray([]int{42})
|
||||||
|
if got != 0 {
|
||||||
|
t.Errorf("expected 0, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- two elements ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_TwoElementsProfit(t *testing.T) {
|
||||||
|
got := algo.MaxSubarray([]int{1, 10})
|
||||||
|
if got != 9 {
|
||||||
|
t.Errorf("expected 9, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxSubarray_TwoElementsLoss(t *testing.T) {
|
||||||
|
got := algo.MaxSubarray([]int{10, 1})
|
||||||
|
if got != 0 {
|
||||||
|
t.Errorf("expected 0, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- minimum appears late ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_MinInMiddle(t *testing.T) {
|
||||||
|
// dip to 2 at index 3, then rises to 8
|
||||||
|
got := algo.MaxSubarray([]int{5, 4, 3, 2, 8})
|
||||||
|
if got != 6 {
|
||||||
|
t.Errorf("expected 6, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- negative values ---
|
||||||
|
|
||||||
|
func TestMaxSubarray_AllNegative(t *testing.T) {
|
||||||
|
// minimum tracks most negative; no positive spread possible
|
||||||
|
got := algo.MaxSubarray([]int{-5, -3, -1})
|
||||||
|
if got != 4 {
|
||||||
|
t.Errorf("expected 4, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxSubarray_MixedNegativePositive(t *testing.T) {
|
||||||
|
// buy at -3, sell at 5 → 8
|
||||||
|
got := algo.MaxSubarray([]int{2, -3, 1, 5})
|
||||||
|
if got != 8 {
|
||||||
|
t.Errorf("expected 8, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/linear"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- map type ---
|
||||||
|
|
||||||
|
func TestQueueWithMaps(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[map[string]int](3)
|
||||||
|
|
||||||
|
m1 := map[string]int{"a": 1}
|
||||||
|
m2 := map[string]int{"b": 2, "c": 3}
|
||||||
|
var nilMap map[string]int
|
||||||
|
|
||||||
|
q.Add(m1)
|
||||||
|
q.Add(m2)
|
||||||
|
q.Add(nilMap)
|
||||||
|
|
||||||
|
if q.Size() != 3 {
|
||||||
|
t.Fatalf("expected size 3, got %d", q.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := q.Peek()
|
||||||
|
if !ok || got["a"] != 1 {
|
||||||
|
t.Errorf("Peek returned wrong map: %v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
pulled, err := q.Pull()
|
||||||
|
if err != nil || pulled["a"] != 1 {
|
||||||
|
t.Errorf("Pull returned wrong map: %v err: %v", pulled, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.Size() != 2 {
|
||||||
|
t.Errorf("expected size 2 after Pull, got %d", q.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueMapMutationAfterEnqueue(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[map[string]int](2)
|
||||||
|
|
||||||
|
m := map[string]int{"x": 10}
|
||||||
|
q.Add(m)
|
||||||
|
|
||||||
|
// mutate original map after enqueue — queue holds a reference
|
||||||
|
m["x"] = 999
|
||||||
|
|
||||||
|
peeked, _ := q.Peek()
|
||||||
|
if peeked["x"] != 999 {
|
||||||
|
t.Logf("map mutation not reflected (value copied): got %d", peeked["x"])
|
||||||
|
} else {
|
||||||
|
t.Logf("map mutation reflected (reference held): got %d", peeked["x"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueMapOverCapacity(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[map[string]int](1)
|
||||||
|
q.Add(map[string]int{"a": 1})
|
||||||
|
|
||||||
|
err := q.Add(map[string]int{"b": 2})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error when adding to full map queue, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- struct (object) type ---
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueWithStructs(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[Person](3)
|
||||||
|
|
||||||
|
q.Add(Person{"Alice", 30})
|
||||||
|
q.Add(Person{"Bob", 25})
|
||||||
|
q.Add(Person{}) // zero value struct
|
||||||
|
|
||||||
|
if q.Size() != 3 {
|
||||||
|
t.Fatalf("expected size 3, got %d", q.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
first, err := q.Pull()
|
||||||
|
if err != nil || first.Name != "Alice" {
|
||||||
|
t.Errorf("expected Alice, got %v", first)
|
||||||
|
}
|
||||||
|
|
||||||
|
last, err := q.Cull()
|
||||||
|
if err != nil || last.Name != "" {
|
||||||
|
t.Errorf("expected zero-value struct at tail, got %v", last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueStructOverCapacity(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[Person](2)
|
||||||
|
q.Add(Person{"A", 1})
|
||||||
|
q.Add(Person{"B", 2})
|
||||||
|
|
||||||
|
err := q.Add(Person{"C", 3})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error adding struct beyond capacity, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pointer type ---
|
||||||
|
|
||||||
|
func TestQueueWithPointers(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[*Person](3)
|
||||||
|
|
||||||
|
p1 := &Person{"Alice", 30}
|
||||||
|
p2 := &Person{"Bob", 25}
|
||||||
|
|
||||||
|
q.Add(p1)
|
||||||
|
q.Add(p2)
|
||||||
|
q.Add(nil) // nil pointer
|
||||||
|
|
||||||
|
if q.Size() != 3 {
|
||||||
|
t.Fatalf("expected size 3, got %d", q.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := q.Peek()
|
||||||
|
if !ok || got != p1 {
|
||||||
|
t.Errorf("Peek: expected p1 pointer, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueNilPointerPull(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[*Person](2)
|
||||||
|
q.Add(nil)
|
||||||
|
|
||||||
|
got, err := q.Pull()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error pulling nil pointer: %v", err)
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("expected nil pointer back, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueuePointerMutationAfterEnqueue(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[*Person](1)
|
||||||
|
|
||||||
|
p := &Person{"Alice", 30}
|
||||||
|
q.Add(p)
|
||||||
|
|
||||||
|
p.Name = "Mutated"
|
||||||
|
|
||||||
|
peeked, _ := q.Peek()
|
||||||
|
if peeked.Name != "Mutated" {
|
||||||
|
t.Errorf("expected mutation to be reflected via pointer, got %s", peeked.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- slice type ---
|
||||||
|
|
||||||
|
func TestQueueWithSlices(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[[]int](3)
|
||||||
|
|
||||||
|
q.Add([]int{1, 2, 3})
|
||||||
|
q.Add([]int{}) // empty slice
|
||||||
|
q.Add(nil) // nil slice
|
||||||
|
|
||||||
|
if q.Size() != 3 {
|
||||||
|
t.Fatalf("expected size 3, got %d", q.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
first, err := q.Pull()
|
||||||
|
if err != nil || len(first) != 3 || first[0] != 1 {
|
||||||
|
t.Errorf("unexpected first slice: %v", first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueSliceMutationAfterEnqueue(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[[]int](1)
|
||||||
|
|
||||||
|
s := []int{1, 2, 3}
|
||||||
|
q.Add(s)
|
||||||
|
|
||||||
|
s[0] = 999
|
||||||
|
|
||||||
|
peeked, _ := q.Peek()
|
||||||
|
if peeked[0] != 999 {
|
||||||
|
t.Logf("slice mutation not reflected (copied): got %d", peeked[0])
|
||||||
|
} else {
|
||||||
|
t.Logf("slice mutation reflected (shared backing array): got %d", peeked[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueSliceOverCapacity(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[[]int](1)
|
||||||
|
q.Add([]int{1})
|
||||||
|
|
||||||
|
err := q.Add([]int{2})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error adding slice beyond capacity, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- empty queue edge cases across all types ---
|
||||||
|
|
||||||
|
func TestQueuePullEmptyMap(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[map[string]int](2)
|
||||||
|
_, err := q.Pull()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pulling from empty map queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueuePullEmptyPointer(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[*Person](2)
|
||||||
|
_, err := q.Pull()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pulling from empty pointer queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueuePullEmptySlice(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[[]int](2)
|
||||||
|
_, err := q.Pull()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pulling from empty slice queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueCullEmptyStruct(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[Person](2)
|
||||||
|
_, err := q.Cull()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error culling from empty struct queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueuePeekEmptyPointer(t *testing.T) {
|
||||||
|
q := linear.QueueFixed[*Person](2)
|
||||||
|
val, ok := q.Peek()
|
||||||
|
if ok || val != nil {
|
||||||
|
t.Errorf("expected (nil, false) from empty pointer queue Peek, got (%v, %v)", val, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/linear"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- map type ---
|
||||||
|
|
||||||
|
func TestStackWithMaps(t *testing.T) {
|
||||||
|
s := linear.StackFixed[map[string]int](3)
|
||||||
|
|
||||||
|
m1 := map[string]int{"a": 1}
|
||||||
|
m2 := map[string]int{"b": 2, "c": 3}
|
||||||
|
var nilMap map[string]int
|
||||||
|
|
||||||
|
s.Push(m1)
|
||||||
|
s.Push(m2)
|
||||||
|
s.Push(nilMap)
|
||||||
|
|
||||||
|
if s.Size() != 3 {
|
||||||
|
t.Fatalf("expected 3 items, got %d", s.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top != nil {
|
||||||
|
t.Errorf("expected nil map on top, got %v", top)
|
||||||
|
}
|
||||||
|
|
||||||
|
popped, err := s.Pop()
|
||||||
|
if err != nil || popped != nil {
|
||||||
|
t.Errorf("expected nil map from Pop, got %v err: %v", popped, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Size() != 2 {
|
||||||
|
t.Errorf("expected 2 items after Pop, got %d", s.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackMapMutationAfterPush(t *testing.T) {
|
||||||
|
s := linear.StackFixed[map[string]int](2)
|
||||||
|
|
||||||
|
m := map[string]int{"x": 10}
|
||||||
|
s.Push(m)
|
||||||
|
|
||||||
|
m["x"] = 999
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top["x"] != 999 {
|
||||||
|
t.Logf("map mutation not reflected (value copied): got %d", top["x"])
|
||||||
|
} else {
|
||||||
|
t.Logf("map mutation reflected (reference held): got %d", top["x"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackMapOverCapacity(t *testing.T) {
|
||||||
|
s := linear.StackFixed[map[string]int](1)
|
||||||
|
s.Push(map[string]int{"a": 1})
|
||||||
|
|
||||||
|
err := s.Push(map[string]int{"b": 2})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pushing map beyond capacity, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- struct (object) type ---
|
||||||
|
|
||||||
|
func TestStackWithStructs(t *testing.T) {
|
||||||
|
s := linear.StackFixed[Person](3)
|
||||||
|
|
||||||
|
s.Push(Person{"Alice", 30})
|
||||||
|
s.Push(Person{"Bob", 25})
|
||||||
|
s.Push(Person{}) // zero value
|
||||||
|
|
||||||
|
if s.Size() != 3 {
|
||||||
|
t.Fatalf("expected 3 items, got %d", s.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top.Name != "" {
|
||||||
|
t.Errorf("expected zero-value struct on top, got %v", top)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom := s.First()
|
||||||
|
if bottom.Name != "Alice" {
|
||||||
|
t.Errorf("expected Alice at bottom, got %v", bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
popped, err := s.Pop()
|
||||||
|
if err != nil || popped.Name != "" {
|
||||||
|
t.Errorf("expected zero-value struct from Pop, got %v", popped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackStructOverCapacity(t *testing.T) {
|
||||||
|
s := linear.StackFixed[Person](2)
|
||||||
|
s.Push(Person{"A", 1})
|
||||||
|
s.Push(Person{"B", 2})
|
||||||
|
|
||||||
|
err := s.Push(Person{"C", 3})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pushing struct beyond capacity, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackClearStructs(t *testing.T) {
|
||||||
|
s := linear.StackFixed[Person](3)
|
||||||
|
s.Push(Person{"Alice", 30})
|
||||||
|
s.Push(Person{"Bob", 25})
|
||||||
|
|
||||||
|
s.Clear()
|
||||||
|
|
||||||
|
if s.Size() != 0 {
|
||||||
|
t.Errorf("expected empty stack after Clear, got %d items", s.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pointer type ---
|
||||||
|
|
||||||
|
func TestStackWithPointers(t *testing.T) {
|
||||||
|
s := linear.StackFixed[*Person](3)
|
||||||
|
|
||||||
|
p1 := &Person{"Alice", 30}
|
||||||
|
p2 := &Person{"Bob", 25}
|
||||||
|
|
||||||
|
s.Push(p1)
|
||||||
|
s.Push(p2)
|
||||||
|
s.Push(nil)
|
||||||
|
|
||||||
|
if s.Size() != 3 {
|
||||||
|
t.Fatalf("expected 3 items, got %d", s.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top != nil {
|
||||||
|
t.Errorf("expected nil pointer on top, got %v", top)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom := s.First()
|
||||||
|
if bottom != p1 {
|
||||||
|
t.Errorf("expected p1 at bottom, got %v", bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackNilPointerPop(t *testing.T) {
|
||||||
|
s := linear.StackFixed[*Person](2)
|
||||||
|
s.Push(nil)
|
||||||
|
|
||||||
|
got, err := s.Pop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error popping nil pointer: %v", err)
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("expected nil pointer back, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPointerMutationAfterPush(t *testing.T) {
|
||||||
|
s := linear.StackFixed[*Person](1)
|
||||||
|
|
||||||
|
p := &Person{"Alice", 30}
|
||||||
|
s.Push(p)
|
||||||
|
|
||||||
|
p.Name = "Mutated"
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top.Name != "Mutated" {
|
||||||
|
t.Errorf("expected mutation reflected via pointer, got %s", top.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- slice type ---
|
||||||
|
|
||||||
|
func TestStackWithSlices(t *testing.T) {
|
||||||
|
s := linear.StackFixed[[]int](3)
|
||||||
|
|
||||||
|
s.Push([]int{1, 2, 3})
|
||||||
|
s.Push([]int{})
|
||||||
|
s.Push(nil)
|
||||||
|
|
||||||
|
if s.Size() != 3 {
|
||||||
|
t.Fatalf("expected 3 items, got %d", s.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top != nil {
|
||||||
|
t.Errorf("expected nil slice on top, got %v", top)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom := s.First()
|
||||||
|
if len(bottom) != 3 || bottom[0] != 1 {
|
||||||
|
t.Errorf("expected [1 2 3] at bottom, got %v", bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackSliceMutationAfterPush(t *testing.T) {
|
||||||
|
s := linear.StackFixed[[]int](1)
|
||||||
|
|
||||||
|
sl := []int{1, 2, 3}
|
||||||
|
s.Push(sl)
|
||||||
|
|
||||||
|
sl[0] = 999
|
||||||
|
|
||||||
|
top := s.Peek()
|
||||||
|
if top[0] != 999 {
|
||||||
|
t.Logf("slice mutation not reflected (copied): got %d", top[0])
|
||||||
|
} else {
|
||||||
|
t.Logf("slice mutation reflected (shared backing array): got %d", top[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackSliceOverCapacity(t *testing.T) {
|
||||||
|
s := linear.StackFixed[[]int](1)
|
||||||
|
s.Push([]int{1})
|
||||||
|
|
||||||
|
err := s.Push([]int{2})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error pushing slice beyond capacity, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- empty stack edge cases ---
|
||||||
|
|
||||||
|
func TestStackPopEmpty(t *testing.T) {
|
||||||
|
s := linear.StackFixed[map[string]int](2)
|
||||||
|
_, err := s.Pop()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error popping from empty map stack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPopEmptyPointer(t *testing.T) {
|
||||||
|
s := linear.StackFixed[*Person](2)
|
||||||
|
_, err := s.Pop()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error popping from empty pointer stack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPopEmptySlice(t *testing.T) {
|
||||||
|
s := linear.StackFixed[[]int](2)
|
||||||
|
_, err := s.Pop()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error popping from empty slice stack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPopEmptyStruct(t *testing.T) {
|
||||||
|
s := linear.StackFixed[Person](2)
|
||||||
|
_, err := s.Pop()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error popping from empty struct stack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek and First have no bounds check — they panic on empty stacks.
|
||||||
|
// These tests document that behavior.
|
||||||
|
|
||||||
|
func TestStackPeekEmptyPanics(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Error("expected panic from Peek on empty stack, got none")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s := linear.StackFixed[*Person](2)
|
||||||
|
s.Peek()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackFirstEmptyPanics(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Error("expected panic from First on empty stack, got none")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s := linear.StackFixed[*Person](2)
|
||||||
|
s.First()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user