diff --git a/Python/algorithms/sorting/tim_sort.py b/Python/algorithms/sorting/tim_sort.py new file mode 100644 index 00000000..0d1d636b --- /dev/null +++ b/Python/algorithms/sorting/tim_sort.py @@ -0,0 +1,120 @@ +# ๐Ÿ“Œ Tim Sort Algorithm +# Language: Python +# Category: Sorting +# Time Complexity: O(n log n) +# Space Complexity: O(n) + +""" +Tim Sort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, +designed to perform well on many kinds of real-world data. It is the default sorting +algorithm used in Python's list.sort() and sorted() functions. + +It works by dividing the array into blocks called "runs". These runs are then sorted +using insertion sort, and then merged using a modified merge sort. +""" + +MIN_MERGE = 32 + +def calc_min_run(n): + """Returns the minimum length of a run for Tim Sort.""" + r = 0 + while n >= MIN_MERGE: + r |= n & 1 + n >>= 1 + return n + r + +def insertion_sort(arr, left, right): + """Sorts a subarray using insertion sort.""" + for i in range(left + 1, right + 1): + j = i + while j > left and arr[j] < arr[j - 1]: + arr[j], arr[j - 1] = arr[j - 1], arr[j] + j -= 1 + +def merge(arr, l, m, r): + """Merges two sorted subarrays arr[l..m] and arr[m+1..r].""" + len1, len2 = m - l + 1, r - m + left, right = [], [] + for i in range(0, len1): + left.append(arr[l + i]) + for i in range(0, len2): + right.append(arr[m + 1 + i]) + + i, j, k = 0, 0, l + + while i < len1 and j < len2: + if left[i] <= right[j]: + arr[k] = left[i] + i += 1 + else: + arr[k] = right[j] + j += 1 + k += 1 + + while i < len1: + arr[k] = left[i] + k += 1 + i += 1 + + while j < len2: + arr[k] = right[j] + k += 1 + j += 1 + +def tim_sort(arr): + """ + Sorts the input list 'arr' using the Tim Sort algorithm. + :param arr: list of elements (int/float) to be sorted + :return: sorted list + """ + n = len(arr) + + if n == 0: # Handle empty array + return arr + + min_run = calc_min_run(n) + + # Sort individual runs of size MIN_MERGE or less using insertion sort + for i in range(0, n, min_run): + insertion_sort(arr, i, min((i + min_run - 1), n - 1)) + + # Start merging from size MIN_MERGE. It will merge + # to form size 2*MIN_MERGE, then 4*MIN_MERGE, and so on. + size = min_run + while size < n: + for left in range(0, n, 2 * size): + mid = min((left + size - 1), (n - 1)) + right = min((left + 2 * size - 1), (n - 1)) + + if mid < right: + merge(arr, left, mid, right) + size *= 2 + + return arr + +# ๐Ÿงช Example usage +if __name__ == "__main__": + sample_array = [38, 27, 43, 3, 9, 82, 10] + print("Original array:", sample_array) + sorted_array = tim_sort(sample_array) + print("Sorted array:", sorted_array) + + sample_array_2 = [5, 4, 3, 2, 1] + print("Original array 2:", sample_array_2) + sorted_array_2 = tim_sort(sample_array_2) + print("Sorted array 2:", sorted_array_2) + + sample_array_3 = [] + print("Original array 3:", sample_array_3) + sorted_array_3 = tim_sort(sample_array_3) + print("Sorted array 3:", sorted_array_3) + + sample_array_4 = [1] + print("Original array 4:", sample_array_4) + sorted_array_4 = tim_sort(sample_array_4) + print("Sorted array 4:", sorted_array_4) + + sample_array_5 = [1, 2, 3, 4, 5] + print("Original array 5:", sample_array_5) + sorted_array_5 = tim_sort(sample_array_5) + print("Sorted array 5:", sorted_array_5) \ No newline at end of file diff --git a/go/cache/inmemory_cache.go b/go/cache/inmemory_cache.go deleted file mode 100644 index 29e43c83..00000000 --- a/go/cache/inmemory_cache.go +++ /dev/null @@ -1,283 +0,0 @@ -// This is a generic, thread-safe in-memory cache implementation in Go, designed to store key-value pairs with optional expiration times. It supports: - -// Generic keys (strings only) and generic values (any) - -// Per-item expiration and default expiration - -// Automatic cleanup of expired items via a background goroutine - -// Safe concurrent access using sync.RWMutex - -// Common cache operations: Set, Get, Update, Delete, Flush, Count, List, MapToCache - -// Designed to be efficient: O(1) access time for most operations - -// It is suitable for caching data in-memory within a single Go application and provides a lightweight alternative to external caching systems like Redis for local use cases. - -package inmemory_cache - -import ( - "errors" - "fmt" - "runtime" - "sync" - "time" -) - -const ( - NoExpiration time.Duration = -1 // Constant to indicate no expiration - Defaultexpires time.Duration = 0 // Constant to use default expiration time -) - -// Item represents a cache entry holding a value and its expiration timestamp -type Item[V any] struct { - value V // Stored value - expires int64 // Expiration time in UnixNano (0 or negative means no expiration) -} - -// Internal cache struct (not exposed directly) -type cache[K ~string, V any] struct { - mu sync.RWMutex // Mutex for thread-safe access - items map[K]*Item[V] // Actual map storing cache items - done chan struct{} // Channel to stop cleanup goroutine - expTime time.Duration // Default expiration time for items - cleanupInt time.Duration // Interval between cleanup runs -} - -// Exposed Cache type embedding the internal cache -type Cache[K ~string, V any] struct { - *cache[K, V] -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Creates a new internal cache instance -func newCache[K ~string, V any](expTime, cleanupInt time.Duration, item map[K]*Item[V]) *cache[K, V] { - c := &cache[K, V]{ - mu: sync.RWMutex{}, - items: item, - expTime: expTime, - cleanupInt: cleanupInt, - done: make(chan struct{}), - } - return c -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Public API: creates and returns a new cache instance with optional background cleanup -func New[K ~string, V any](expTime, cleanupTime time.Duration) *Cache[K, V] { - items := make(map[K]*Item[V]) - c := newCache(expTime, cleanupTime, items) - - if cleanupTime > 0 { - // Start background cleanup - go c.cleanup() - // Set finalizer to stop cleanup when cache is garbage collected - runtime.SetFinalizer(c, stopCleanup[K, V]) - } - - return &Cache[K, V]{c} -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Adds a new item to the cache, returns error if key already exists -func (c *Cache[K, V]) Set(key K, val V, d time.Duration) error { - item, err := c.Get(key) - if item != nil && err == nil { - return fmt.Errorf("item with key '%v' already exists. Use the Update method", key) - } - c.add(key, val, d) - return nil -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Adds a value using default expiration -func (c *Cache[K, V]) SetDefault(key K, val V) error { - return c.Set(key, val, Defaultexpires) -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Internal method to add or update a cache item -func (c *Cache[K, V]) add(key K, val V, d time.Duration) error { - var exp int64 - - // Handle expiration logic - if d == Defaultexpires { - d = c.expTime - } - if d > 0 { - exp = time.Now().Add(d).UnixNano() - } else if d < 0 { - exp = int64(NoExpiration) - } - - // Prevent overwriting existing items - item, err := c.Get(key) - if item != nil && err != nil { - return fmt.Errorf("item with key '%v' already exists", key) - } - - // Optional value validation - switch any(val).(type) { - case string: - if len(any(val).(string)) == 0 { - return fmt.Errorf("value of type string cannot be empty") - } - } - - // Add to map with write lock - c.mu.Lock() - c.items[key] = &Item[V]{value: val, expires: exp} - c.mu.Unlock() - - return nil -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Retrieves a cache item if it exists and is not expired -func (c *Cache[K, V]) Get(key K) (*Item[V], error) { - c.mu.RLock() - if item, ok := c.items[key]; ok { - if item.expires > 0 { - now := time.Now().UnixNano() - if now > item.expires { - c.mu.RUnlock() - return nil, fmt.Errorf("item with key '%v' expired", key) - } - } - c.mu.RUnlock() - return item, nil - } - c.mu.RUnlock() - return nil, fmt.Errorf("item with key '%v' not found", key) -} - -// Safely retrieves the value from an Item -func (it *Item[V]) Val() V { - var v V - if it != nil { - return it.value - } - return v -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Updates an existing cache item -func (c *Cache[K, V]) Update(key K, val V, d time.Duration) error { - item, err := c.Get(key) - if item != nil && err != nil { - return err - } - return c.add(key, val, d) -} // Time Complexity: O(1) -// Space Complexity: O(1) - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Deletes a cache item -func (c *Cache[K, V]) Delete(key K) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.delete(key) -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Internal delete logic -func (c *cache[K, V]) delete(key K) error { - if _, ok := c.items[key]; ok { - delete(c.items, key) - return nil - } - return fmt.Errorf("item with key '%v' does not exists", key) -} - -// Deletes all expired items from the cache -func (c *cache[K, V]) DeleteExpired() error { - var err error - now := time.Now().UnixNano() - - c.mu.Lock() - for k, item := range c.items { - if now > item.expires && item.expires != int64(NoExpiration) { - if e := c.delete(k); e != nil { - err = errors.Join(err, e) - } - } - } - c.mu.Unlock() - - return errors.Unwrap(err) -} - -// Clears all items from the cache -func (c *Cache[K, V]) Flush() { - c.mu.Lock() - c.items = make(map[K]*Item[V]) - c.mu.Unlock() -} - -// Returns the underlying items map (shallow copy not made) -func (c *Cache[K, V]) List() map[K]*Item[V] { - c.mu.RLock() - defer c.mu.RUnlock() - return c.items -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Returns the count of items in the cache -func (c *Cache[K, V]) Count() int { - c.mu.RLock() - n := len(c.items) - c.mu.RUnlock() - return n -} - -// Bulk inserts items into the cache -func (c *Cache[K, V]) MapToCache(m map[K]V, d time.Duration) error { - var err error - for k, v := range m { - e := c.Set(k, v, d) - err = errors.Join(err, e) - } - return errors.Unwrap(err) -} - -// Checks if a specific key is expired -func (c *Cache[K, V]) IsExpired(key K) bool { - item, err := c.Get(key) - if item != nil && err != nil { - if item.expires > time.Now().UnixNano() { - return true - } - } - return false -} - -// Background cleanup process to remove expired items periodically -func (c *cache[K, V]) cleanup() { - tick := time.NewTicker(c.cleanupInt) - for { - select { - case <-tick.C: - c.DeleteExpired() - case <-c.done: - tick.Stop() - return - } - } -} - -// Time Complexity: O(1) -// Space Complexity: O(1) -// Finalizer to stop cleanup goroutine when cache is GC'd -func stopCleanup[K ~string, V any](c *cache[K, V]) { - c.done <- struct{}{} -} diff --git a/go/cache/inmemory_cache_test.go b/go/cache/inmemory_cache_test.go deleted file mode 100644 index 66eef91f..00000000 --- a/go/cache/inmemory_cache_test.go +++ /dev/null @@ -1,49 +0,0 @@ -//this is to test inmemory_cache.go - -package inmemory_cache - -import ( - "testing" - "time" -) - -type TestStruct struct { - Num int - Children []*TestStruct -} - -func TestCache(t *testing.T) { - tc := New[string, int](NoExpiration, 0) - - a, found := tc.Get("a") - if found == nil { - t.Error("Getting A found value that shouldn't exist:", a) - } - - b, found := tc.Get("b") - if found == nil { - t.Error("Getting B found value that shouldn't exist:", b) - } - - c, found := tc.Get("c") - if found == nil { - t.Error("Getting C found value that shouldn't exist:", c) - } - - tc.Set("a", 1, NoExpiration) - - x, found := tc.Get("a") - if found != nil { - t.Error("a was not found while getting a2:", x) - } - -} - -func TestCacheTimes(t *testing.T) { - - tc := New[string, int](50*time.Millisecond, 1*time.Millisecond) - tc.Set("a", 1, Defaultexpires) - tc.Set("b", 2, NoExpiration) - tc.Set("c", 3, 20*time.Millisecond) - tc.Set("d", 4, 70*time.Millisecond) -} diff --git a/go/ciphers/ceaser_cipher.go b/go/ciphers/ceaser_cipher.go deleted file mode 100644 index 7ddf5ade..00000000 --- a/go/ciphers/ceaser_cipher.go +++ /dev/null @@ -1,31 +0,0 @@ -// time complexity: O(n) -// space complexity: O(n) - -package caesar - -// Encrypt encrypts by right shift of "key" each character of "input" -func Encrypt(input string, key int) string { - // if key is negative value, - // updates "key" the number which congruents to "key" modulo 26 - key8 := byte(key%26+26) % 26 - - var outputBuffer []byte - // b is a byte, which is the equivalent of uint8. - for _, b := range []byte(input) { - newByte := b - if 'A' <= b && b <= 'Z' { - outputBuffer = append(outputBuffer, 'A'+(newByte-'A'+key8)%26) - } else if 'a' <= b && b <= 'z' { - outputBuffer = append(outputBuffer, 'a'+(newByte-'a'+key8)%26) - } else { - outputBuffer = append(outputBuffer, newByte) - } - } - return string(outputBuffer) -} - -// Decrypt decrypts by left shift of "key" each character of "input" -func Decrypt(input string, key int) string { - // left shift of "key" is same as right shift of 26-"key" - return Encrypt(input, 26-key) -} diff --git a/go/ciphers/ceaser_cipher_test.go b/go/ciphers/ceaser_cipher_test.go deleted file mode 100644 index 1e22a8c3..00000000 --- a/go/ciphers/ceaser_cipher_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package caesar - -import ( - "fmt" - "math/rand" - "testing" - "time" -) - -func TestEncrypt(t *testing.T) { - var caesarTestData = []struct { - description string - input string - key int - expected string - }{ - { - "Basic caesar encryption with letter 'a'", - "a", - 3, - "d", - }, - { - "Basic caesar encryption wrap around alphabet on letter 'z'", - "z", - 3, - "c", - }, - { - "Encrypt a simple string with caesar encryiption", - "hello", - 3, - "khoor", - }, - { - "Encrypt a simple string with key 13", - "hello", - 13, - "uryyb", - }, - { - "Encrypt a simple string with key -13", - "hello", - -13, - "uryyb", - }, - { - "With key of 26 output should be the same as the input", - "no change", - 26, - "no change", - }, - { - "Encrypt sentence with key 10", - "the quick brown fox jumps over the lazy dog.", - 10, - "dro aesmu lbygx pyh tewzc yfob dro vkji nyq.", - }, - { - "Encrypt sentence with key 10", - "The Quick Brown Fox Jumps over the Lazy Dog.", - 10, - "Dro Aesmu Lbygx Pyh Tewzc yfob dro Vkji Nyq.", - }, - } - for _, test := range caesarTestData { - t.Run(test.description, func(t *testing.T) { - actual := Encrypt(test.input, test.key) - if actual != test.expected { - t.Logf("FAIL: %s", test.description) - t.Fatalf("With input string '%s' and key '%d' was expecting '%s' but actual was '%s'", - test.input, test.key, test.expected, actual) - } - }) - } -} - -func TestDecrypt(t *testing.T) { - var caesarTestData = []struct { - description string - input string - key int - expected string - }{ - { - "Basic caesar decryption with letter 'a'", - "a", - 3, - "x", - }, - { - "Basic caesar decryption wrap around alphabet on letter 'z'", - "z", - 3, - "w", - }, - { - "Decrypt a simple string with caesar encryiption", - "hello", - 3, - "ebiil", - }, - { - "Decrypt a simple string with key 13", - "hello", - 13, - "uryyb", - }, - { - "Decrypt a simple string with key -13", - "hello", - -13, - "uryyb", - }, - { - "With key of 26 output should be the same as the input", - "no change", - 26, - "no change", - }, - { - "Decrypt sentence with key 10", - "Dro Aesmu Lbygx Pyh Tewzc yfob dro Vkji Nyq.", - 10, - "The Quick Brown Fox Jumps over the Lazy Dog.", - }, - } - - for _, test := range caesarTestData { - t.Run(test.description, func(t *testing.T) { - actual := Decrypt(test.input, test.key) - if actual != test.expected { - t.Logf("FAIL: %s", test.description) - t.Fatalf("With input string '%s' and key '%d' was expecting '%s' but actual was '%s'", - test.input, test.key, test.expected, actual) - } - }) - } -} - -func Example() { - const ( - key = 10 - input = "The Quick Brown Fox Jumps over the Lazy Dog." - ) - - encryptedText := Encrypt(input, key) - fmt.Printf("Encrypt=> key: %d, input: %s, encryptedText: %s\n", key, input, encryptedText) - - decryptedText := Decrypt(encryptedText, key) - fmt.Printf("Decrypt=> key: %d, input: %s, decryptedText: %s\n", key, encryptedText, decryptedText) - - // Output: - // Encrypt=> key: 10, input: The Quick Brown Fox Jumps over the Lazy Dog., encryptedText: Dro Aesmu Lbygx Pyh Tewzc yfob dro Vkji Nyq. - // Decrypt=> key: 10, input: Dro Aesmu Lbygx Pyh Tewzc yfob dro Vkji Nyq., decryptedText: The Quick Brown Fox Jumps over the Lazy Dog. -} - -func FuzzCaesar(f *testing.F) { - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - f.Add("The Quick Brown Fox Jumps over the Lazy Dog.") - f.Fuzz(func(t *testing.T, input string) { - key := rnd.Intn(26) - if result := Decrypt(Encrypt(input, key), key); result != input { - t.Fatalf("With input: '%s' and key: %d\n\tExpected: '%s'\n\tGot: '%s'", input, key, input, result) - } - }) -} diff --git a/go/ciphers/rsa_cipher.go b/go/ciphers/rsa_cipher.go deleted file mode 100644 index bdf8c1e6..00000000 --- a/go/ciphers/rsa_cipher.go +++ /dev/null @@ -1,40 +0,0 @@ -// Description:This Go program generates an RSA key pair, encrypts a message using the public key with OAEP padding and SHA-256, and then decrypts it using the private key to recover the original message. - -package rsa_cipher - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "fmt" -) - -func main() { - // Step 1: Generate RSA Key Pair (2048 bits) - //Time complexity : O(k^3) where k is size of the key in bits - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - panic(err) - } - publicKey := &privateKey.PublicKey - - // Step 2: Encrypt a message using the public key - //Time complexity : O(k^2) - message := []byte("Hello, RSA in Go!") - label := []byte("") // Optional label for OAEP - hash := sha256.New() - - ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, publicKey, message, label) - if err != nil { - panic(err) - } - fmt.Printf("Encrypted message: %x\n", ciphertext) - - // Step 3: Decrypt the message using the private key - //Time complexity : O(k^3) - plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, privateKey, ciphertext, label) - if err != nil { - panic(err) - } - fmt.Printf("Decrypted message: %s\n", string(plaintext)) -} diff --git a/go/compression/huffman_coding.go b/go/compression/huffman_coding.go deleted file mode 100644 index 8ab96a4e..00000000 --- a/go/compression/huffman_coding.go +++ /dev/null @@ -1,111 +0,0 @@ -// time complexity: O(n) -// space complexity: O(n) - -package compression - -import "fmt" - -// A Node of an Huffman tree, which can either be a leaf or an internal node. -// Each node has a weight. -// A leaf node has an associated symbol, but no children (i.e., left == right == nil). -// A parent node has a left and right child and no symbol (i.e., symbol == -1). -type Node struct { - left *Node - right *Node - symbol rune - weight int -} - -// A SymbolFreq is a pair of a symbol and its associated frequency. -type SymbolFreq struct { - Symbol rune - Freq int -} - -// HuffTree returns the root Node of the Huffman tree by compressing listfreq. -// The compression produces the most optimal code lengths, provided listfreq is ordered, -// i.e.: listfreq[i] <= listfreq[j], whenever i < j. -func HuffTree(listfreq []SymbolFreq) (*Node, error) { - if len(listfreq) < 1 { - return nil, fmt.Errorf("huffman coding: HuffTree : calling method with empty list of symbol-frequency pairs") - } - q1 := make([]Node, len(listfreq)) - q2 := make([]Node, 0, len(listfreq)) - for i, x := range listfreq { // after the loop, q1 is a slice of leaf nodes representing listfreq - q1[i] = Node{left: nil, right: nil, symbol: x.Symbol, weight: x.Freq} - } - //loop invariant: q1, q2 are ordered by increasing weights - for len(q1)+len(q2) > 1 { - var node1, node2 Node - node1, q1, q2 = least(q1, q2) - node2, q1, q2 = least(q1, q2) - node := Node{left: &node1, right: &node2, - symbol: -1, weight: node1.weight + node2.weight} - q2 = append(q2, node) - } - if len(q1) == 1 { // returns the remaining node in q1, q2 - return &q1[0], nil - } - return &q2[0], nil -} - -// least removes the node with lowest weight from q1, q2. -// It returns the node with lowest weight and the slices q1, q2 after the update. -func least(q1 []Node, q2 []Node) (Node, []Node, []Node) { - if len(q1) == 0 { - return q2[0], q1, q2[1:] - } - if len(q2) == 0 { - return q1[0], q1[1:], q2 - } - if q1[0].weight <= q2[0].weight { - return q1[0], q1[1:], q2 - } - return q2[0], q1, q2[1:] -} - -// HuffEncoding recursively traverses the Huffman tree pointed by node to obtain -// the map codes, that associates a rune with a slice of booleans. -// Each code is prefixed by prefix and left and right children are labelled with -// the booleans false and true, respectively. -func HuffEncoding(node *Node, prefix []bool, codes map[rune][]bool) { - if node.symbol != -1 { //base case - codes[node.symbol] = prefix - return - } - // inductive step - prefixLeft := make([]bool, len(prefix)) - copy(prefixLeft, prefix) - prefixLeft = append(prefixLeft, false) - HuffEncoding(node.left, prefixLeft, codes) - prefixRight := make([]bool, len(prefix)) - copy(prefixRight, prefix) - prefixRight = append(prefixRight, true) - HuffEncoding(node.right, prefixRight, codes) -} - -// HuffEncode encodes the string in by applying the mapping defined by codes. -func HuffEncode(codes map[rune][]bool, in string) []bool { - out := make([]bool, 0) - for _, s := range in { - out = append(out, codes[s]...) - } - return out -} - -// HuffDecode recursively decodes the binary code in, by traversing the Huffman compression tree pointed by root. -// current stores the current node of the traversing algorithm. -// out stores the current decoded string. -func HuffDecode(root, current *Node, in []bool, out string) string { - if current.symbol != -1 { - out += string(current.symbol) - return HuffDecode(root, root, in, out) - } - if len(in) == 0 { - return out - } - if in[0] { - return HuffDecode(root, current.right, in[1:], out) - } - return HuffDecode(root, current.left, in[1:], out) -} diff --git a/go/graph/dijkstras_algorithm.go b/go/graph/dijkstras_algorithm.go deleted file mode 100644 index 14fa62f3..00000000 --- a/go/graph/dijkstras_algorithm.go +++ /dev/null @@ -1,255 +0,0 @@ -//Dijkstra's algorithm is a graph search algorithm used to find the shortest path from a starting node to all other nodes in a weighted graph (with non-negative edge weights). - -package main - -import ( - "fmt" - "math" - "os" - "strconv" - "sync" -) - -func main() { - // Build graph and run Dijkstra's algorithm - graph := buildGraph() - - // Start city is passed as command-line argument - city := os.Args[1] - dijkstra(graph, city) - - // Display shortest path from start city to all other cities - for _, node := range graph.Nodes { - fmt.Printf("Shortest time from %s to %s is %d\n", - city, node.name, node.value) - for n := node; n.through != nil; n = n.through { - fmt.Print(n, " <- ") - } - fmt.Println(city) - fmt.Println() - } -} - -// dijkstra runs Dijkstra's algorithm on the graph from the given city. -// Time Complexity: O((V + E) * log V) where V is number of nodes and E is number of edges. -func dijkstra(graph *WeightedGraph, city string) { - visited := make(map[string]bool) - heap := &Heap{} - - startNode := graph.GetNode(city) - startNode.value = 0 - heap.Push(startNode) - - for heap.Size() > 0 { - current := heap.Pop() - visited[current.name] = true - - edges := graph.Edges[current.name] - for _, edge := range edges { - if !visited[edge.node.name] { - heap.Push(edge.node) - // Relaxation step - if current.value+edge.weight < edge.node.value { - edge.node.value = current.value + edge.weight - edge.node.through = current - } - } - } - } -} - -// buildGraph creates a sample undirected, weighted graph. -// Time Complexity: O(N + E) where N is number of nodes and E is number of edges. -func buildGraph() *WeightedGraph { - graph := NewGraph() - nodes := AddNodes(graph, - "London", "Paris", "Amsterdam", "Luxembourg", "Zurich", - "Rome", "Berlin", "Vienna", "Warsaw", "Istanbul", - ) - - // Add edges (bidirectional) - graph.AddEdge(nodes["London"], nodes["Paris"], 80) - graph.AddEdge(nodes["London"], nodes["Luxembourg"], 75) - graph.AddEdge(nodes["London"], nodes["Amsterdam"], 75) - graph.AddEdge(nodes["Paris"], nodes["Luxembourg"], 60) - graph.AddEdge(nodes["Paris"], nodes["Rome"], 125) - graph.AddEdge(nodes["Luxembourg"], nodes["Berlin"], 90) - graph.AddEdge(nodes["Luxembourg"], nodes["Zurich"], 60) - graph.AddEdge(nodes["Luxembourg"], nodes["Amsterdam"], 55) - graph.AddEdge(nodes["Zurich"], nodes["Vienna"], 80) - graph.AddEdge(nodes["Zurich"], nodes["Rome"], 90) - graph.AddEdge(nodes["Zurich"], nodes["Berlin"], 85) - graph.AddEdge(nodes["Berlin"], nodes["Amsterdam"], 85) - graph.AddEdge(nodes["Berlin"], nodes["Vienna"], 75) - graph.AddEdge(nodes["Vienna"], nodes["Rome"], 100) - graph.AddEdge(nodes["Vienna"], nodes["Istanbul"], 130) - graph.AddEdge(nodes["Warsaw"], nodes["Berlin"], 80) - graph.AddEdge(nodes["Warsaw"], nodes["Istanbul"], 180) - graph.AddEdge(nodes["Rome"], nodes["Istanbul"], 155) - - return graph -} - -// ---------- Weighted Graph Definitions ---------- - -type Node struct { - name string // City name - value int // Distance from source - through *Node // Previous node in shortest path -} - -type Edge struct { - node *Node // Target node - weight int // Edge weight -} - -type WeightedGraph struct { - Nodes []*Node - Edges map[string][]*Edge - mutex sync.RWMutex -} - -func NewGraph() *WeightedGraph { - return &WeightedGraph{ - Edges: make(map[string][]*Edge), - } -} - -// GetNode returns the pointer to the node with given name -// Time Complexity: O(N) -func (g *WeightedGraph) GetNode(name string) (node *Node) { - g.mutex.RLock() - defer g.mutex.RUnlock() - for _, n := range g.Nodes { - if n.name == name { - node = n - } - } - return -} - -// AddNode adds a node to the graph -func (g *WeightedGraph) AddNode(n *Node) { - g.mutex.Lock() - defer g.mutex.Unlock() - g.Nodes = append(g.Nodes, n) -} - -// AddNodes adds multiple nodes to the graph -// Time Complexity: O(N) -func AddNodes(graph *WeightedGraph, names ...string) (nodes map[string]*Node) { - nodes = make(map[string]*Node) - for _, name := range names { - n := &Node{name, math.MaxInt, nil} // Default distance is "infinity" - graph.AddNode(n) - nodes[name] = n - } - return -} - -// AddEdge adds a bidirectional edge to the graph -// Time Complexity: O(1) -func (g *WeightedGraph) AddEdge(n1, n2 *Node, weight int) { - g.mutex.Lock() - defer g.mutex.Unlock() - g.Edges[n1.name] = append(g.Edges[n1.name], &Edge{n2, weight}) - g.Edges[n2.name] = append(g.Edges[n2.name], &Edge{n1, weight}) -} - -func (n *Node) String() string { - return n.name -} - -func (e *Edge) String() string { - return e.node.String() + "(" + strconv.Itoa(e.weight) + ")" -} - -// ---------- Heap (Min-Heap based on Node.value) ---------- - -type Heap struct { - elements []*Node - mutex sync.RWMutex -} - -// Size returns number of elements in heap -// Time Complexity: O(1) -func (h *Heap) Size() int { - h.mutex.RLock() - defer h.mutex.RUnlock() - return len(h.elements) -} - -// Push adds a node into the heap and maintains heap order -// Time Complexity: O(log N) -func (h *Heap) Push(element *Node) { - h.mutex.Lock() - defer h.mutex.Unlock() - h.elements = append(h.elements, element) - i := len(h.elements) - 1 - // Bubble up to restore min-heap - for ; i > 0 && h.elements[i].value < h.elements[parent(i)].value; i = parent(i) { - h.swap(i, parent(i)) - } -} - -// Pop removes and returns the node with smallest value (min-heap root) -// Time Complexity: O(log N) -func (h *Heap) Pop() (i *Node) { - h.mutex.Lock() - defer h.mutex.Unlock() - if len(h.elements) == 0 { - return nil - } - i = h.elements[0] - last := len(h.elements) - 1 - h.elements[0] = h.elements[last] - h.elements = h.elements[:last] - h.rearrange(0) - return -} - -// rearrange restores heap order starting from index i -// Time Complexity: O(log N) -func (h *Heap) rearrange(i int) { - smallest := i - left, right, size := leftChild(i), rightChild(i), len(h.elements) - if left < size && h.elements[left].value < h.elements[smallest].value { - smallest = left - } - if right < size && h.elements[right].value < h.elements[smallest].value { - smallest = right - } - if smallest != i { - h.swap(i, smallest) - h.rearrange(smallest) - } -} - -func (h *Heap) swap(i, j int) { - h.elements[i], h.elements[j] = h.elements[j], h.elements[i] -} - -func parent(i int) int { - return (i - 1) / 2 -} - -func leftChild(i int) int { - return 2*i + 1 -} - -func rightChild(i int) int { - return 2*i + 2 -} - -// String representation of heap contents -func (h *Heap) String() (str string) { - return fmt.Sprintf("%q\n", getNames(h.elements)) -} - -// getNames returns a slice of node names -func getNames(nodes []*Node) (names []string) { - for _, node := range nodes { - names = append(names, node.name) - } - return -} diff --git a/go/load_balancer/round_robin.go b/go/load_balancer/round_robin.go deleted file mode 100644 index 8e489ef1..00000000 --- a/go/load_balancer/round_robin.go +++ /dev/null @@ -1,124 +0,0 @@ -package load_balancer - -import ( - "fmt" - "net/http" - "net/http/httputil" - "net/url" - "os" -) - -// Server interface defines behavior of a backend server -type Server interface { - Address() string // Returns server address - IsAlive() bool // Checks if server is alive - Serve(http.ResponseWriter, *http.Request) // Proxies the request to the server -} - -// simpleServer is a concrete implementation of the Server interface -type simpleServer struct { - addr string - proxy *httputil.ReverseProxy -} - -// Address returns the server address -func (s *simpleServer) Address() string { - return s.addr -} - -// IsAlive returns true (can be expanded to health check) -func (s *simpleServer) IsAlive() bool { - return true -} - -// Serve handles the incoming HTTP request and proxies it to the backend -func (s *simpleServer) Serve(rw http.ResponseWriter, req *http.Request) { - s.proxy.ServeHTTP(rw, req) -} - -// newSimpleServer initializes a simpleServer with a reverse proxy -func newSimpleServer(addr string) *simpleServer { - serverUrl, err := url.Parse(addr) - handleErr(err) - - return &simpleServer{ - addr: addr, - proxy: httputil.NewSingleHostReverseProxy(serverUrl), - } -} - -// LoadBalancer handles distributing traffic among backend servers -type LoadBalancer struct { - port string // Port to listen on - roundRobinCount int // Counter for round-robin selection - servers []Server // List of backend servers -} - -// NewLoadBalancer initializes a load balancer with given port and server list -func NewLoadBalancer(port string, servers []Server) *LoadBalancer { - return &LoadBalancer{ - port: port, - roundRobinCount: 0, - servers: servers, - } -} - -// handleErr prints the error and exits if an error occurred -func handleErr(err error) { - if err != nil { - fmt.Printf("error: %v\n", err) - os.Exit(1) - } -} - -// getNextAvailableServer selects the next alive server using round-robin -// Time complexity : O(n) -// Space complexity : O(1) -// if all previous servers are down and we loop through multiple times. -func (lb *LoadBalancer) getNextAvailableServer() Server { - server := lb.servers[lb.roundRobinCount%len(lb.servers)] - for !server.IsAlive() { - lb.roundRobinCount++ - server = lb.servers[lb.roundRobinCount%len(lb.servers)] - } - lb.roundRobinCount++ - - return server -} - -// serveProxy forwards the HTTP request to a selected backend server - -func (lb *LoadBalancer) serveProxy(rw http.ResponseWriter, req *http.Request) { - targetServer := lb.getNextAvailableServer() - - fmt.Printf("forwarding request to address %q\n", targetServer.Address()) - - targetServer.Serve(rw, req) -} - -// main function starts the HTTP server and initializes the load balancer -func main() { - // Define backend servers here - servers := []Server{ - newSimpleServer("http://localhost:9001"), - // newSimpleServer("http://localhost:9002"), - // newSimpleServer("http://localhost:9003"), - } - - // Create a new load balancer - lb := NewLoadBalancer("8000", servers) - - // Define the handler to forward incoming requests - handleRedirect := func(rw http.ResponseWriter, req *http.Request) { - lb.serveProxy(rw, req) - } - - // Register the handler for root path - http.HandleFunc("/", handleRedirect) - - fmt.Printf("serving requests at 'localhost:%s'\n", lb.port) - - // Start HTTP server - err := http.ListenAndServe(":"+lb.port, nil) - handleErr(err) -}