From 6e09f5b861674049c52e88e3b1a6ee29f5aadef6 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Tue, 17 Oct 2023 15:57:35 +1100 Subject: [PATCH 1/4] Dev --- .codespellignore | 1 + .pre-commit-config.yaml | 1 + hashtable.go | 149 ++++++++++++++++++++++++++ hashtable_test.go | 226 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 .codespellignore 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..8e6beea 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -1,6 +1,7 @@ package hashtable_test import ( + "fmt" "testing" "github.com/lindsaygelle/hashtable" @@ -13,7 +14,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 +110,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 +177,185 @@ 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 less than 7 + ht.DeleteFunc(func(key string, value int) bool { + return value < 7 + }) + + // 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]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Function to print all key-value pairs. + var printedKeys []string + printKeyValue := func(key string, value int) { + printedKeys = append(printedKeys, key) + fmt.Println(key, value) + } + + // Iterate over the hashtable and print all key-value pairs. + ht.Each(printKeyValue) + // Output: "apple 5", "banana 3", "cherry 8" + + // Check if the printed keys are correct + expectedKeys := []string{"apple", "banana", "cherry"} + for i, key := range expectedKeys { + if printedKeys[i] != key { + t.Errorf("Expected key %s, but got %s", key, printedKeys[i]) + } + } + + // Check if other keys are not printed + if len(printedKeys) > 3 { + t.Errorf("Expected only 'apple', 'banana', and 'cherry' to be printed, but got more keys.") + } +} + +// TestEachBreak tests Hashtable.EachBreak. +func TestEachBreak(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Function to print key-value pairs until finding "banana". + var printedKeys []string + ht.EachBreak(func(key string, value int) bool { + printedKeys = append(printedKeys, key) + fmt.Println(key, value) + return key != "banana" // Continue printing until "banana" is encountered. + }) + // Output: "apple 5", "banana 3" + + // Check if the printed keys are correct + expectedKeys := []string{"apple", "banana"} + for i, key := range expectedKeys { + if printedKeys[i] != key { + t.Errorf("Expected key %s, but got %s", key, printedKeys[i]) + } + } + + // Check if other keys are not printed + if len(printedKeys) > 2 { + t.Errorf("Expected only 'apple' and 'banana' to be printed, but got more keys.") + } +} + +// TestEachKey tests Hashtable.EachKey. +func TestEachKey(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Function to collect each key in a slice. + var collectedKeys []string + collectKey := func(key string) { + collectedKeys = append(collectedKeys, key) + } + + // Iterate over the hashtable and collect each key. + ht.EachKey(collectKey) + + // Check if the collected keys are correct + expectedKeys := []string{"apple", "banana", "cherry"} + if len(collectedKeys) != len(expectedKeys) { + t.Errorf("Expected %d keys, but got %d", len(expectedKeys), len(collectedKeys)) + } + for i, key := range expectedKeys { + if collectedKeys[i] != key { + t.Errorf("Expected key %s, but got %s", key, collectedKeys[i]) + } + } +} + +func TestEachKeyBreak(t *testing.T) { + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Function to print each key and break the iteration if the key is "banana". + var collectedKeys []string + printAndBreak := func(key string) bool { + collectedKeys = append(collectedKeys, key) + fmt.Println(key) + return key != "banana" + } + + // Iterate over the hashtable keys, print them, and break when "banana" is encountered. + ht.EachKeyBreak(printAndBreak) + + // Check if the collected keys and the printed output are correct. + expectedKeys := []string{"apple", "banana"} + if len(collectedKeys) != len(expectedKeys) { + t.Errorf("Expected %d keys, but got %d", len(expectedKeys), len(collectedKeys)) + } + for i, key := range expectedKeys { + if collectedKeys[i] != key { + t.Errorf("Expected key %s, but got %s", key, collectedKeys[i]) + } + } +} + +// TestGet tests Hashtable.Get. func TestGet(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) From b138b394985386928c02c74143c5dda1426ffff5 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Tue, 17 Oct 2023 16:18:58 +1100 Subject: [PATCH 2/4] Dev --- hashtable_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashtable_test.go b/hashtable_test.go index 8e6beea..606d494 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -184,9 +184,9 @@ func TestDeleteFunc(t *testing.T) { ht["orange"] = 7 ht["kiwi"] = 6 - // Delete key-value pairs where the value is less than 7 + // Delete key-value pairs where the value is 7 ht.DeleteFunc(func(key string, value int) bool { - return value < 7 + return value == 6 }) // Check if the key-value pair with key "kiwi" is removed From 0e5fec67068cc377abd1197ac37b752585466272 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Tue, 17 Oct 2023 16:37:55 +1100 Subject: [PATCH 3/4] Dev --- hashtable_test.go | 120 ++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/hashtable_test.go b/hashtable_test.go index 606d494..2b7aa92 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -1,7 +1,6 @@ package hashtable_test import ( - "fmt" "testing" "github.com/lindsaygelle/hashtable" @@ -239,118 +238,113 @@ func TestDeleteMany(t *testing.T) { // 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 - // Function to print all key-value pairs. - var printedKeys []string - printKeyValue := func(key string, value int) { - printedKeys = append(printedKeys, key) - fmt.Println(key, value) + // Define a map to store the expected output. + expectedOutput := map[string]int{ + "apple": 5, + "banana": 3, + "cherry": 8, } - // Iterate over the hashtable and print all key-value pairs. - ht.Each(printKeyValue) - // Output: "apple 5", "banana 3", "cherry 8" - - // Check if the printed keys are correct - expectedKeys := []string{"apple", "banana", "cherry"} - for i, key := range expectedKeys { - if printedKeys[i] != key { - t.Errorf("Expected key %s, but got %s", key, printedKeys[i]) + // 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) } } - // Check if other keys are not printed - if len(printedKeys) > 3 { - t.Errorf("Expected only 'apple', 'banana', and 'cherry' to be printed, but got more keys.") + // 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]) - ht["apple"] = 5 - ht["banana"] = 3 - ht["cherry"] = 8 - // Function to print key-value pairs until finding "banana". - var printedKeys []string + // 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 { - printedKeys = append(printedKeys, key) - fmt.Println(key, value) - return key != "banana" // Continue printing until "banana" is encountered. + t.Logf("%s %d", key, value) + stopPrinting = key + return key != "banana" }) - // Output: "apple 5", "banana 3" - - // Check if the printed keys are correct - expectedKeys := []string{"apple", "banana"} - for i, key := range expectedKeys { - if printedKeys[i] != key { - t.Errorf("Expected key %s, but got %s", key, printedKeys[i]) - } - } - // Check if other keys are not printed - if len(printedKeys) > 2 { - t.Errorf("Expected only 'apple' and 'banana' to be printed, but got more keys.") + // 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 - // Function to collect each key in a slice. - var collectedKeys []string - collectKey := func(key string) { - collectedKeys = append(collectedKeys, key) + // Define a function to print each key. + var printedKeys []string + printKey := func(key string) { + printedKeys = append(printedKeys, key) } - // Iterate over the hashtable and collect each key. - ht.EachKey(collectKey) + // Iterate over the keys and print each key. + ht.EachKey(printKey) - // Check if the collected keys are correct + // Expected output: "apple", "banana", "cherry" expectedKeys := []string{"apple", "banana", "cherry"} - if len(collectedKeys) != len(expectedKeys) { - t.Errorf("Expected %d keys, but got %d", len(expectedKeys), len(collectedKeys)) - } - for i, key := range expectedKeys { - if collectedKeys[i] != key { - t.Errorf("Expected key %s, but got %s", key, collectedKeys[i]) + for i, key := range printedKeys { + if key != expectedKeys[i] { + t.Errorf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) } } } 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 - // Function to print each key and break the iteration if the key is "banana". - var collectedKeys []string + // Define a function to print each key and break if the key is "banana". + var printedKeys []string printAndBreak := func(key string) bool { - collectedKeys = append(collectedKeys, key) - fmt.Println(key) + printedKeys = append(printedKeys, key) return key != "banana" } - // Iterate over the hashtable keys, print them, and break when "banana" is encountered. + // Iterate over the keys and print each key, breaking if the key is "banana". ht.EachKeyBreak(printAndBreak) - // Check if the collected keys and the printed output are correct. + // Expected output: "apple", "banana" expectedKeys := []string{"apple", "banana"} - if len(collectedKeys) != len(expectedKeys) { - t.Errorf("Expected %d keys, but got %d", len(expectedKeys), len(collectedKeys)) - } - for i, key := range expectedKeys { - if collectedKeys[i] != key { - t.Errorf("Expected key %s, but got %s", key, collectedKeys[i]) + for i, key := range printedKeys { + if key != expectedKeys[i] { + t.Errorf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) } } } From 1db4e9ddca9db1e0900a427c17311440a6fd38af Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Tue, 17 Oct 2023 16:38:29 +1100 Subject: [PATCH 4/4] Dev --- hashtable_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hashtable_test.go b/hashtable_test.go index 2b7aa92..d6d4340 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -322,6 +322,7 @@ func TestEachKey(t *testing.T) { } } +// TestEachKeyBreak tests Hashtable.EachKeyBreak. func TestEachKeyBreak(t *testing.T) { ht := make(hashtable.Hashtable[string, int])