diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 0000000..f13df21 --- /dev/null +++ b/.codespellignore @@ -0,0 +1 @@ +readded diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 975d2f2..7bc7e0c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,3 +19,4 @@ repos: rev: v2.1.0 hooks: - id: codespell + args: [--ignore-words=.codespellignore] diff --git a/hashtable.go b/hashtable.go index 4ed763f..a01487a 100644 --- a/hashtable.go +++ b/hashtable.go @@ -69,6 +69,24 @@ func (hashtable *Hashtable[K, V]) AddMany(values ...map[K]V) *Hashtable[K, V] { return hashtable } +// AddOK inserts a new key-value pair into the hashtable if the key does not already exist. +// It returns a boolean value indicating whether the key was added successfully (true) or if the key already existed (false). +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// +// // Attempt to add key-value pairs. +// added := ht.AddOK("apple", 5) // added is true, "apple" is added with value 5. +// reAdded := ht.AddOK("apple", 10) // reAdded is false, "apple" already exists with value 5, no change is made. +// addedNew := ht.AddOK("banana", 3) // addedNew is true, "banana" is added with value 3. +func (hashtable *Hashtable[K, V]) AddOK(key K, value V) bool { + ok := !hashtable.Has(key) + if ok { + hashtable.Add(key, value) + } + return ok +} + // Delete removes a key-value pair from the hashtable based on the provided key. // // Example: @@ -81,6 +99,137 @@ func (hashtable *Hashtable[K, V]) Delete(key K) *Hashtable[K, V] { return hashtable } +// DeleteFunc removes key-value pairs from the hashtable based on the evaluation performed by the provided function. +// For each key-value pair in the hashtable, the function fn is called with the key and value. +// If the function returns true, the key-value pair is removed from the hashtable. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("orange", 7) +// ht.Add("kiwi", 6) +// +// // Delete key-value pairs where the value is less than 7. +// ht.DeleteFunc(func(key string, value int) bool { +// return value < 7 +// }) // Removes the key-value pair with key "kiwi" from the hashtable. +func (hashtable *Hashtable[K, V]) DeleteFunc(fn func(key K, value V) bool) *Hashtable[K, V] { + for key, value := range *hashtable { + if fn(key, value) { + hashtable.Delete(key) + } + } + return hashtable +} + +// DeleteMany removes multiple key-value pairs from the hashtable based on the provided keys. +// If a key doesn't exist in the hashtable, it is ignored. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("orange", 7) +// ht.Add("kiwi", 6) +// ht.DeleteMany("apple", "kiwi") // Removes key-value pairs with keys "apple" and "kiwi" from the hashtable. +func (hashtable *Hashtable[K, V]) DeleteMany(keys ...K) *Hashtable[K, V] { + for _, key := range keys { + hashtable.Delete(key) + } + return hashtable +} + +// Each iterates over the key-value pairs in the hashtable and applies a function to each pair. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to print all key-value pairs. +// printKeyValue := func(key string, value int) { +// fmt.Println(key, value) +// } +// +// // Iterate over the hashtable and print all key-value pairs. +// ht.Each(printKeyValue) +// // Output: "apple 5", "banana 3", "cherry 8" +func (hashtable *Hashtable[K, V]) Each(fn func(key K, value V)) *Hashtable[K, V] { + return hashtable.EachBreak(func(key K, value V) bool { + fn(key, value) + return true + }) +} + +// EachBreak iterates over the key-value pairs in the hashtable and applies a function to each pair. +// If the function returns false at any point, the iteration breaks. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to print key-value pairs until finding "banana". +// stopPrinting := ht.EachBreak(func(key string, value int) bool { +// fmt.Println(key, value) +// return key != "banana" // Continue printing until "banana" is encountered. +// }) +// // Output: "apple 5", "banana 3" +func (hashtable *Hashtable[K, V]) EachBreak(fn func(key K, value V) bool) *Hashtable[K, V] { + for key, value := range *hashtable { + if !fn(key, value) { + break + } + } + return hashtable +} + +// EachKey iterates over the keys in the hashtable and applies a function to each key. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to print each key. +// printKey := func(key string) { +// fmt.Println(key) +// } +// +// // Iterate over the hashtable and print each key. +// ht.EachKey(printKey) +// // Output: "apple", "banana", "cherry" +func (hashtable *Hashtable[K, V]) EachKey(fn func(key K)) *Hashtable[K, V] { + return hashtable.Each(func(key K, _ V) { + fn(key) + }) +} + +// EachKeyBreak iterates over the keys in the hashtable and applies a function to each key. It allows breaking the iteration early if the provided function returns false. +// +// Example: +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to print each key and break the iteration if the key is "banana". +// printAndBreak := func(key string) bool { +// fmt.Println(key) +// return key != "banana" +// } +// +// // Iterate over the hashtable keys, print them, and break when "banana" is encountered. +// ht.EachKeyBreak(printAndBreak) +// // Output: "apple", "banana" +func (hashtable *Hashtable[K, V]) EachKeyBreak(fn func(key K) bool) *Hashtable[K, V] { + return hashtable.EachBreak(func(key K, _ V) bool { + return fn(key) + }) +} + // Get retrieves the value associated with the provided key from the hashtable. // If the key exists, it returns the associated value and true. Otherwise, it returns the zero value for the value type and false. // diff --git a/hashtable_test.go b/hashtable_test.go index 12d0f53..d6d4340 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -13,7 +13,7 @@ func TestHashtable(t *testing.T) { t.Log(table) } -// TestAdd tests Hashtable.Add +// TestAdd tests Hashtable.Add. func TestAdd(t *testing.T) { // Create a new hashtable ht := make(hashtable.Hashtable[string, int]) @@ -109,7 +109,48 @@ func TestAddMany(t *testing.T) { } } -// TestDelete tests Hashtable.Delete +// TestAddOK tests Hashtable.AddOK. +func TestAddOK(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + + // Test adding a new key-value pair + added := ht.AddOK("apple", 5) + if !added { + t.Error("Expected 'apple' to be added, but it was not.") + } + + // Check if the key-value pair is added correctly + value, exists := ht["apple"] + if !exists || value != 5 { + t.Errorf("Expected key 'apple' with value '5', but got value '%d'", value) + } + + // Test adding an existing key + reAdded := ht.AddOK("apple", 10) + if reAdded { + t.Error("Expected 'apple' to not be re-added, but it was.") + } + + // Check if the value for 'apple' remains unchanged + value, exists = ht["apple"] + if !exists || value != 5 { + t.Errorf("Expected key 'apple' to have value '5' after re-adding attempt, but got value '%d'", value) + } + + // Test adding another new key-value pair + addedNew := ht.AddOK("banana", 3) + if !addedNew { + t.Error("Expected 'banana' to be added, but it was not.") + } + + // Check if the key-value pair for 'banana' is added correctly + value, exists = ht["banana"] + if !exists || value != 3 { + t.Errorf("Expected key 'banana' with value '3', but got value '%d'", value) + } +} + +// TestDelete tests Hashtable.Delete. func TestDelete(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) ht["apple"] = 5 @@ -135,7 +176,181 @@ func TestDelete(t *testing.T) { } } -// TestGet tests Hashtable.Get +// TestDeleteFunc tests Hashtable.DeleteFunc. +func TestDeleteFunc(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["orange"] = 7 + ht["kiwi"] = 6 + + // Delete key-value pairs where the value is 7 + ht.DeleteFunc(func(key string, value int) bool { + return value == 6 + }) + + // Check if the key-value pair with key "kiwi" is removed + _, exists := ht["kiwi"] + if exists { + t.Error("Expected key 'kiwi' to be removed, but it still exists.") + } + + // Check if other key-value pairs are still present + _, exists = ht["apple"] + if !exists { + t.Error("Expected key 'apple' to be present, but it is not.") + } + + _, exists = ht["orange"] + if !exists { + t.Error("Expected key 'orange' to be present, but it is not.") + } +} + +// TestDeleteMany tests Hashtable.DeleteMany. +func TestDeleteMany(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["orange"] = 7 + ht["kiwi"] = 6 + + // Delete key-value pairs with keys "apple" and "kiwi" + ht.DeleteMany("apple", "kiwi") + + // Check if the key-value pair with key "apple" is removed + _, exists := ht["apple"] + if exists { + t.Error("Expected key 'apple' to be removed, but it still exists.") + } + + // Check if the key-value pair with key "kiwi" is removed + _, exists = ht["kiwi"] + if exists { + t.Error("Expected key 'kiwi' to be removed, but it still exists.") + } + + // Check if other key-value pairs are still present + _, exists = ht["orange"] + if !exists { + t.Error("Expected key 'orange' to be present, but it is not.") + } +} + +// TestEach tests Hashtable.Each. +func TestEach(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + + // Add key-value pairs to the hashtable. + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Define a map to store the expected output. + expectedOutput := map[string]int{ + "apple": 5, + "banana": 3, + "cherry": 8, + } + + // Define a function to compare the actual output with the expected output. + printKeyValue := func(key string, value int) { + if expected, ok := expectedOutput[key]; ok { + if value != expected { + t.Errorf("Expected %s: %d, but got %d", key, expected, value) + } + delete(expectedOutput, key) + } else { + t.Errorf("Unexpected key: %s", key) + } + } + + // Call the Each function with the printKeyValue function. + ht.Each(printKeyValue) + + // Check if all expected keys have been processed. + if len(expectedOutput) > 0 { + t.Errorf("Not all keys were processed: %v", expectedOutput) + } +} + +// TestEachBreak tests Hashtable.EachBreak. +func TestEachBreak(t *testing.T) { + var stopPrinting string + ht := make(hashtable.Hashtable[string, int]) + + // Add key-value pairs to the hashtable. + ht.Add("apple", 5) + ht.Add("banana", 3) + ht.Add("cherry", 8) + + // Define a function to stop iteration when key is "banana". + ht.EachBreak(func(key string, value int) bool { + t.Logf("%s %d", key, value) + stopPrinting = key + return key != "banana" + }) + + // Ensure that iteration stopped at "banana". + if stopPrinting != "banana" { + t.Errorf("Iteration did not stop at 'banana'.") + } +} + +// TestEachKey tests Hashtable.EachKey. +func TestEachKey(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + + // Add key-value pairs to the hashtable. + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Define a function to print each key. + var printedKeys []string + printKey := func(key string) { + printedKeys = append(printedKeys, key) + } + + // Iterate over the keys and print each key. + ht.EachKey(printKey) + + // Expected output: "apple", "banana", "cherry" + expectedKeys := []string{"apple", "banana", "cherry"} + for i, key := range printedKeys { + if key != expectedKeys[i] { + t.Errorf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) + } + } +} + +// TestEachKeyBreak tests Hashtable.EachKeyBreak. +func TestEachKeyBreak(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + + // Add key-value pairs to the hashtable. + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Define a function to print each key and break if the key is "banana". + var printedKeys []string + printAndBreak := func(key string) bool { + printedKeys = append(printedKeys, key) + return key != "banana" + } + + // Iterate over the keys and print each key, breaking if the key is "banana". + ht.EachKeyBreak(printAndBreak) + + // Expected output: "apple", "banana" + expectedKeys := []string{"apple", "banana"} + for i, key := range printedKeys { + if key != expectedKeys[i] { + t.Errorf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) + } + } +} + +// TestGet tests Hashtable.Get. func TestGet(t *testing.T) { ht := make(hashtable.Hashtable[string, int])