Go Algorithms

Updated at
package main

import (
	"errors"
	"fmt"
	"math"
)

// https://www.toptal.com/developers/sorting-algorithms
// O rule #1: Growth is with respect to the input
// O rule #2: Constants are dropped O(2n) = O(n)
// O rule #3: Worst case is usually the way we measure
// O rule #4: If the input halves at each step, its likelu O(logN) or O(NlogN)

// Linear search O(n)
// Scans each haystack element in order to find needle.
func linearSearch(haystack []int, needle int) int {
	for i, v := range haystack {
		if v == needle {
			return i
		}
	}
	return -1
}

// Binary search O(logN)
// For n = 16 it takes 4 operations log2(16) = 4
// [ 01 02 04 08 16 32 64 ]
// -----------| N/2
// -----| N/4
// Math induction formula: N/2^k = 1 -> N = 2^k -> log2(N) = k
func binarySearch(haystack []int, needle int) int {
	lo, hi := 0, len(haystack)
	for lo < hi {
		m := lo + int((hi-lo)/2)
		if haystack[m] == needle {
			return m
		} else if haystack[m] < needle {
			lo = m + 1 // needle is on the right side
		} else {
			hi = m // needle is on the left side (exclusive)
		}
	}
	return -1
}

// Given two crystal balls that will break if dropped from high enough distance,
// determine the exact spot in which it will break in the most optimized way.
// binary + linear will give us O(n), intead we can use O(sqrt(N))
// in the worst case scenario, we will walk sqrt(N) times, instead of constant value
func crystalBalls(floors []int) int {
	// First ball
	step := int(math.Sqrt(float64(len(floors))))
	i := step
	for ; i < len(floors); i += step {
		if floors[i] == 1 {
			break
		}
	}
	// Second ball
	i -= step
	for ; i < len(floors); i++ {
		if floors[i] == 1 {
			return i
		}
	}
	return -1
}

// Bubble Sort O(n^2)
// For each N you need to compare it with the next value
// For each N you have an extra inner loop, which gives you ^2
// [1, 4, 8, 2, 0]
// [1, 4, 2, 0,|8] N
// [1, 2, 0,|4, 8] N - 1
// [1, 0,|2, 4, 8] N - 2
// [0,|1, 2, 4, 8] N - N + 1
func bubbleSort(arr []int) {
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr)-1-i; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
}

// Quick Sort O(NlogN)
func quickSort(arr []int) {
	qsSort(arr, 0, len(arr)-1)
}

func qsSort(arr []int, lo, hi int) {
	if lo >= hi {
		return
	}
	pivotIdx := qsPartition(arr, lo, hi)
	qsSort(arr, lo, pivotIdx-1)
	qsSort(arr, pivotIdx+1, hi)
}

func qsPartition(arr []int, lo, hi int) int {
	pivot, idx := arr[hi], lo
	for i := lo; i < hi; i++ {
		if arr[i] <= pivot {
			arr[i], arr[idx] = arr[idx], arr[i]
			idx++
		}
	}
	arr[hi], arr[idx] = arr[idx], pivot
	return idx
}

func testSearch(haystack []int, fn func([]int, int) int) error {
	for i, v := range haystack {
		if r := fn(haystack, v); i != r {
			return errors.New(fmt.Sprintf(
				"expected: %d, returned: %d, value: %d",
				i, r, v))
		}
	}
	return nil
}

func testSort(arr []int, fn func([]int)) error {
	fn(arr)
	for i := 0; i < len(arr)-1; i++ {
		if arr[i] > arr[i+1] {
			return errors.New(fmt.Sprintf("%v", arr))
		}
	}
	return nil
}

// O(n) for search
type linkedList[T any] interface {
	length() int
	insertAt(item *T, index int)
	remove(item *T) *T
	removeAt(index int) *T
	append(item *T)
	prepand(item *T)
	get(index int) *T
}

// O(1) for everything
type queue[T any] interface {
	length() int
	enqueue(item *T)
	deque() *T
	peek() *T
}

// O(1) for everything
type stack[T any] interface {
	length() int
	push(item *T)
	pop() *T
	peek() *T
}

// O(n) for insert / delete
type arrayList[T any] interface {
	length() int
	capacity() int
}

// (Ring buffer) effective for delete or insert to the head or tail
// tail % len = index from the beginning
// [ . . . . 1 2 4 8 . . . . ]
// 0        [       ]        N
// [--->    h       t    --->]
type arrayBuffer[T any] interface {
}

func main() {
	if err := testSearch([]int{1, 2, 4, 8, 16, 32, 64, 128}, linearSearch); err != nil {
		fmt.Println("Linear Search:", err)
	}
	if err := testSearch([]int{1, 2, 4, 8, 16, 32, 64, 128}, binarySearch); err != nil {
		fmt.Println("Binary Search:", err)
	}
	if cb := crystalBalls([]int{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1}); cb != 8 {
		fmt.Printf("Crystal Balls: expected: 8, value %d", cb)
	}
	if err := testSort([]int{1, 4, 8, 2, 0}, bubbleSort); err != nil {
		fmt.Println("Bubble Sort:", err)
	}
	if err := testSort([]int{1, 4, 8, 2, 0, 128, 64}, quickSort); err != nil {
		fmt.Println("Quick Sort:", err)
	}
	fmt.Println("Done!")
}