added tests

This commit is contained in:
Acid
2026-06-08 22:59:57 -04:00
parent 857189c4f8
commit 9a1c3defcb
10 changed files with 353 additions and 15 deletions
+14 -8
View File
@@ -27,6 +27,14 @@
- [ ] Hash Map
- [ ] Hash Set
# 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
# Algorithms ωψγ
- [x] Kadane's Algorithm
@@ -38,14 +46,6 @@
- [ ] DFS Depth-First Search
- [ ] Karatsuba 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
```bash
@@ -55,3 +55,9 @@ go doc -all ./linear | bat -l go
```bash
go doc -all ./algo | bat -l go
```
# Tests
```bash
go test ./tests/...
```
+61
View File
@@ -0,0 +1,61 @@
package tests
import (
"testing"
"datastructures/algo"
)
func TestGCD(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"coprime", 13, 7, 1},
{"common factor", 12, 8, 4},
{"one divides other", 21, 7, 7},
{"equal values", 9, 9, 9},
{"b is zero", 5, 0, 5},
{"a is zero", 0, 5, 5},
{"both zero", 0, 0, 0},
{"a less than b", 8, 12, 4},
{"one", 1, 999, 1},
{"large coprime primes", 17 * 19, 23, 1},
{"large common factor", 1071, 462, 21},
{"powers of two", 1024, 256, 256},
{"consecutive integers are coprime", 100, 101, 1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := algo.GCD(c.a, c.b); got != c.want {
t.Errorf("GCD(%d, %d) = %d, want %d", c.a, c.b, got, c.want)
}
})
}
}
// GCD(a, b) should equal GCD(b, a) for the inputs we support.
func TestGCD_Commutative(t *testing.T) {
pairs := [][2]int{{12, 8}, {21, 7}, {1071, 462}, {13, 7}, {100, 101}}
for _, p := range pairs {
if got, rev := algo.GCD(p[0], p[1]), algo.GCD(p[1], p[0]); got != rev {
t.Errorf("GCD not commutative for (%d, %d): %d != %d", p[0], p[1], got, rev)
}
}
}
// The result must evenly divide both inputs (when non-zero).
func TestGCD_DividesBothInputs(t *testing.T) {
pairs := [][2]int{{12, 8}, {1071, 462}, {1024, 256}, {18, 24}}
for _, p := range pairs {
g := algo.GCD(p[0], p[1])
if g == 0 {
t.Fatalf("GCD(%d, %d) returned 0 for non-zero inputs", p[0], p[1])
}
if p[0]%g != 0 || p[1]%g != 0 {
t.Errorf("GCD(%d, %d) = %d does not divide both inputs", p[0], p[1], g)
}
}
}
+106
View File
@@ -0,0 +1,106 @@
package tests
import (
"testing"
"datastructures/algo"
)
// NOTE on behavior:
// algo.Fib does NOT return the target-th Fibonacci number. For target > 1 it
// advances through the Fibonacci sequence (0,1,1,2,3,5,8,13,...) and returns
// the smallest Fibonacci number that is >= target. For target <= 1 it returns
// target unchanged. These tests pin that actual behavior.
//
// Fib(4) = 5 (smallest fib >= 4), not 3 (the 4th fib)
// Fib(7) = 8 (smallest fib >= 7), not 13
//
// If the intent was "n-th Fibonacci number", the implementation is buggy and
// these expectations should be updated alongside the fix.
func TestFib(t *testing.T) {
cases := []struct {
target int
want int
}{
{0, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 5},
{5, 5},
{6, 8},
{7, 8},
{8, 8},
{9, 13},
{12, 13},
{13, 13},
{14, 21},
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
if got := algo.Fib(c.target); got != c.want {
t.Errorf("Fib(%d) = %d, want %d", c.target, got, c.want)
}
})
}
}
// target <= 1 is returned unchanged, including negative inputs.
func TestFib_SmallAndNegative(t *testing.T) {
cases := []struct {
target, want int
}{
{-5, -5},
{-1, -1},
{0, 0},
{1, 1},
}
for _, c := range cases {
if got := algo.Fib(c.target); got != c.want {
t.Errorf("Fib(%d) = %d, want %d", c.target, got, c.want)
}
}
}
// Property: for target >= 2 the result is a real Fibonacci number and is the
// smallest such number that is >= target.
func TestFib_ReturnsSmallestFibAtOrAboveTarget(t *testing.T) {
// Build the set of Fibonacci numbers up to a comfortable bound.
fibs := []int{0, 1}
for fibs[len(fibs)-1] < 100000 {
n := len(fibs)
fibs = append(fibs, fibs[n-1]+fibs[n-2])
}
isFib := func(v int) bool {
for _, f := range fibs {
if f == v {
return true
}
}
return false
}
smallestFibGE := func(target int) int {
for _, f := range fibs {
if f >= target {
return f
}
}
t.Fatalf("target %d exceeds precomputed fib range", target)
return -1
}
for target := 2; target <= 1000; target++ {
got := algo.Fib(target)
if !isFib(got) {
t.Fatalf("Fib(%d) = %d is not a Fibonacci number", target, got)
}
if got < target {
t.Fatalf("Fib(%d) = %d is below target", target, got)
}
if want := smallestFibGE(target); got != want {
t.Errorf("Fib(%d) = %d, want smallest fib >= target = %d", target, got, want)
}
}
}
+79
View File
@@ -0,0 +1,79 @@
package tests
import (
"testing"
"datastructures/algo"
)
// algo.MaxSubarray implements Kadane-style "max profit": it returns the largest
// value of array[j] - array[i] for i < j, or 0 when no such positive difference
// exists. (It is a buy-low / sell-high spread, not a contiguous-sum.)
func TestMaxSubarray(t *testing.T) {
cases := []struct {
name string
array []int
want int
}{
{"basic profit", []int{7, 1, 5, 3, 6, 4}, 5},
{"profit at end", []int{3, 1, 4, 1, 5, 9}, 8},
{"already sorted", []int{1, 2, 3, 4, 5}, 4},
{"strictly decreasing", []int{5, 4, 3, 2, 1}, 0},
{"all same", []int{3, 3, 3, 3}, 0},
{"single element", []int{42}, 0},
{"two elements profit", []int{1, 10}, 9},
{"two elements loss", []int{10, 1}, 0},
{"min in middle then rises", []int{5, 4, 3, 2, 8}, 6},
{"all negative", []int{-5, -3, -1}, 4},
{"mixed neg/pos", []int{2, -3, 1, 5}, 8},
{"dip late", []int{9, 8, 7, 1, 2}, 1},
{"zero crossing", []int{-1, 0, -2, 3}, 5},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := algo.MaxSubarray(c.array); got != c.want {
t.Errorf("MaxSubarray(%v) = %d, want %d", c.array, got, c.want)
}
})
}
}
// bruteForceMaxProfit is the obvious O(n^2) reference: best later-minus-earlier
// spread, floored at 0.
func bruteForceMaxProfit(array []int) int {
best := 0
for i := 0; i < len(array); i++ {
for j := i + 1; j < len(array); j++ {
if d := array[j] - array[i]; d > best {
best = d
}
}
}
return best
}
// Property: the linear implementation must agree with the brute-force reference
// across a deterministic spread of inputs.
func TestMaxSubarray_MatchesBruteForce(t *testing.T) {
inputs := [][]int{
{1},
{2, 1},
{1, 2},
{4, 1, 7, 2, 9, 3},
{-2, -5, -1, -8, -3},
{0, 0, 0, 1},
{10, -10, 10, -10, 20},
{3, 3, 4, 2, 5, 1, 6},
{-1, 2, -3, 4, -5, 6},
{100, 50, 75, 25, 80},
}
for _, in := range inputs {
want := bruteForceMaxProfit(in)
if got := algo.MaxSubarray(in); got != want {
t.Errorf("MaxSubarray(%v) = %d, brute force = %d", in, got, want)
}
}
}
@@ -0,0 +1,86 @@
package tests
import (
"testing"
"datastructures/linear"
)
// --- Current on an untouched buffer ---
func TestCircularBuffer_CurrentNilWhenEmpty(t *testing.T) {
var cb linear.CircularBuffer[int]
if cb.Current != nil {
t.Errorf("expected Current to be nil on empty buffer, got %v", *cb.Current)
}
}
func TestCircularBuffer_CurrentNilFromConstructor(t *testing.T) {
cb := linear.NewCircularBuffer[int]()
if cb.Current != nil {
t.Errorf("expected Current to be nil from NewCircularBuffer, got %v", *cb.Current)
}
}
// --- Current points at the most recently pushed value ---
func TestCircularBuffer_CurrentAfterOnePush(t *testing.T) {
var cb linear.CircularBuffer[int]
cb.Push(42)
if cb.Current == nil {
t.Fatal("expected Current to be set after Push, got nil")
}
if *cb.Current != 42 {
t.Errorf("expected *Current == 42, got %d", *cb.Current)
}
}
func TestCircularBuffer_CurrentTracksLatest(t *testing.T) {
var cb linear.CircularBuffer[int]
for _, v := range []int{1, 2, 3, 4, 5} {
cb.Push(v)
if *cb.Current != v {
t.Errorf("after pushing %d, expected *Current == %d, got %d", v, v, *cb.Current)
}
}
}
// --- Current aliases the live backing slot ---
func TestCircularBuffer_CurrentAliasesWrittenSlot(t *testing.T) {
var cb linear.CircularBuffer[int]
cb.Push(10)
cb.Push(20)
cb.Push(30)
// Last write landed in slot 2; Data() is a snapshot of the backing array.
if got := cb.Data()[2]; *cb.Current != got {
t.Errorf("*Current (%d) should match Data()[2] (%d)", *cb.Current, got)
}
}
// --- Current follows the write head through a wrap-around ---
func TestCircularBuffer_CurrentAfterOverflow(t *testing.T) {
var cb linear.CircularBuffer[int]
for i := 1; i <= 11; i++ {
cb.Push(i)
}
// The 11th value (11) overwrote slot 0 and is the most recent write.
if *cb.Current != 11 {
t.Errorf("expected *Current == 11 after overflow, got %d", *cb.Current)
}
if got := cb.Data()[0]; *cb.Current != got {
t.Errorf("*Current (%d) should match overwritten slot 0 (%d)", *cb.Current, got)
}
}
// --- Current works for non-int types ---
func TestCircularBuffer_CurrentStringType(t *testing.T) {
var cb linear.CircularBuffer[string]
cb.Push("hello")
cb.Push("world")
if cb.Current == nil || *cb.Current != "world" {
t.Errorf("expected *Current == \"world\", got %v", cb.Current)
}
}
@@ -11,7 +11,7 @@ import (
func intBuffer(vals ...int) linear.CircularBuffer[int] {
var cb linear.CircularBuffer[int]
for _, v := range vals {
cb.Add(v)
cb.Push(v)
}
return cb
}
@@ -108,7 +108,7 @@ func TestCircularBuffer_DoubleWrap(t *testing.T) {
func TestCircularBuffer_StressAdd(t *testing.T) {
var cb linear.CircularBuffer[int]
for i := 1; i <= 100; i++ {
cb.Add(i)
cb.Push(i)
}
// last 10 values added were 91-100
// after 100 adds: tail wraps, slots should hold 91-100
@@ -126,8 +126,8 @@ func TestCircularBuffer_StressAdd(t *testing.T) {
func TestCircularBuffer_StringType(t *testing.T) {
var cb linear.CircularBuffer[string]
cb.Add("hello")
cb.Add("world")
cb.Push("hello")
cb.Push("world")
data := cb.Data()
if data[0] != "hello" {
t.Errorf("expected hello, got %s", data[0])
@@ -141,7 +141,7 @@ 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)
cb.Push(w)
}
// "k" overwrote slot 0 ("a")
if cb.Data()[0] != "k" {
@@ -153,8 +153,8 @@ func TestCircularBuffer_StringOverflow(t *testing.T) {
func TestCircularBuffer_FloatType(t *testing.T) {
var cb linear.CircularBuffer[float64]
cb.Add(3.14)
cb.Add(2.71)
cb.Push(3.14)
cb.Push(2.71)
data := cb.Data()
if data[0] != 3.14 {
t.Errorf("expected 3.14, got %f", data[0])