From 3163de78e1eacdc51f7952c0d8fe0d789da3f078 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Tue, 17 Oct 2023 22:01:34 +1100 Subject: [PATCH 1/3] Dev --- go.mod | 2 + go.sum | 2 + hashtable.go | 362 +++++++++++++++++++++++++++++----------------- hashtable_test.go | 192 ++++++++++++++++-------- 4 files changed, 363 insertions(+), 195 deletions(-) create mode 100644 go.sum diff --git a/go.mod b/go.mod index b4fed8d..c27192b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/lindsaygelle/hashtable go 1.21 + +require github.com/lindsaygelle/slice v1.2.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..80205dd --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/lindsaygelle/slice v1.2.2 h1:/Xn+ki2hFAS9NbnCxjp6+SK7OThYQKHGFV9kag3eheg= +github.com/lindsaygelle/slice v1.2.2/go.mod h1:vWZA76NLCaYC3p0nq10S0/uuOCbU871o9eOnm9tLj60= diff --git a/hashtable.go b/hashtable.go index e0c6e49..35608df 100644 --- a/hashtable.go +++ b/hashtable.go @@ -1,34 +1,42 @@ package hashtable +import ( + "reflect" + + "github.com/lindsaygelle/slice" +) + // Hashtable represents a generic hash table that maps keys of type K to values of type V. It provides efficient key-value storage and retrieval operations. // // Example: -// // Create a new Hashtable with string keys and integer values. -// ht := make(hashtable.Hashtable[string, int]) // -// // Insert key-value pairs into the hashtable. -// ht["apple"] = 5 -// ht["banana"] = 3 -// ht["cherry"] = 8 +// // Create a new Hashtable with string keys and integer values. +// ht := make(hashtable.Hashtable[string, int]) // -// // Retrieve the value associated with a specific key. -// value := ht["banana"] // 3 +// // Insert key-value pairs into the hashtable. +// ht["apple"] = 5 +// ht["banana"] = 3 +// ht["cherry"] = 8 // -// // Check if a key exists in the hashtable. -// _, exists := ht["grape"] +// // Retrieve the value associated with a specific key. +// value := ht["banana"] // 3 // -// // Delete a key-value pair from the hashtable. -// delete(ht, "cherry") +// // Check if a key exists in the hashtable. +// _, exists := ht["grape"] +// +// // Delete a key-value pair from the hashtable. +// delete(ht, "cherry") type Hashtable[K comparable, V any] map[K]V // Add inserts a key-value pair into the hashtable. If the key already exists, its associated value is updated. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// ht.Add("banana", 3) -// ht.Add("cherry", 8) -// ht.Add("banana", 10) // Updates the value associated with the "banana" key. +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// ht.Add("banana", 10) // Updates the value associated with the "banana" key. func (hashtable *Hashtable[K, V]) Add(key K, value V) *Hashtable[K, V] { (*hashtable)[key] = value return hashtable @@ -38,11 +46,12 @@ func (hashtable *Hashtable[K, V]) Add(key K, value V) *Hashtable[K, V] { // The validation function should return true for key-value pairs that should be added to the hashtable, and false otherwise. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.AddFunc([]map[string]int{{"apple": 1, "orange": 2}}, func(key string, value int) bool { -// // Only add key-value pairs where the value is greater than 1. -// return value > 1 -// }) +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.AddFunc([]map[string]int{{"apple": 1, "orange": 2}}, func(key string, value int) bool { +// // Only add key-value pairs where the value is greater than 1. +// return value > 1 +// }) func (hashtable *Hashtable[K, V]) AddFunc(values []map[K]V, fn func(key K, value V) bool) *Hashtable[K, V] { for _, item := range values { for key, value := range item { @@ -58,8 +67,9 @@ func (hashtable *Hashtable[K, V]) AddFunc(values []map[K]V, fn func(key K, value // If keys already exist, their associated values are updated. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.AddMany(map[string]int{"orange": 7, "grape": 4}, map[string]int{"kiwi": 6, "pear": 9}) +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.AddMany(map[string]int{"orange": 7, "grape": 4}, map[string]int{"kiwi": 6, "pear": 9}) func (hashtable *Hashtable[K, V]) AddMany(values ...map[K]V) *Hashtable[K, V] { for _, item := range values { for key, value := range item { @@ -73,12 +83,13 @@ func (hashtable *Hashtable[K, V]) AddMany(values ...map[K]V) *Hashtable[K, V] { // 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. +// 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 { @@ -90,10 +101,11 @@ func (hashtable *Hashtable[K, V]) AddOK(key K, value V) bool { // Delete removes a key-value pair from the hashtable based on the provided key. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// ht.Add("banana", 3) -// ht.Delete("apple") // Removes the key-value pair with key "apple" from the hashtable. +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Delete("apple") // Removes the key-value pair with key "apple" from the hashtable. func (hashtable *Hashtable[K, V]) Delete(key K) *Hashtable[K, V] { delete(*hashtable, key) return hashtable @@ -104,15 +116,16 @@ func (hashtable *Hashtable[K, V]) Delete(key K) *Hashtable[K, V] { // 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. +// +// 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) { @@ -126,11 +139,12 @@ func (hashtable *Hashtable[K, V]) DeleteFunc(fn func(key K, value V) bool) *Hash // 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. +// +// 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) @@ -138,22 +152,48 @@ func (hashtable *Hashtable[K, V]) DeleteMany(keys ...K) *Hashtable[K, V] { return hashtable } +// DeleteManyValues deletes key-value pairs from the hashtable where the value matches any of the specified values. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Delete key-value pairs where the value is 3 or 8. +// ht.DeleteManyValues(3, 8) +// +// // The hashtable after deletion: {"apple": 5} +func (hashtable *Hashtable[K, V]) DeleteManyValues(values ...V) *Hashtable[K, V] { + for key, value := range *hashtable { + for _, v := range values { + if reflect.DeepEqual(v, value) { + hashtable.Delete(key) + break + } + } + } + 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" +// +// 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) @@ -165,17 +205,18 @@ func (hashtable *Hashtable[K, V]) Each(fn func(key K, value V)) *Hashtable[K, V] // 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" +// +// 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) { @@ -188,19 +229,20 @@ func (hashtable *Hashtable[K, V]) EachBreak(fn func(key K, value V) bool) *Hasht // 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" +// +// 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) @@ -210,20 +252,21 @@ func (hashtable *Hashtable[K, V]) EachKey(fn func(key K)) *Hashtable[K, V] { // 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" +// +// 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) @@ -233,19 +276,20 @@ func (hashtable *Hashtable[K, V]) EachKeyBreak(fn func(key K) bool) *Hashtable[K // EachValue iterates over the values in the hashtable and applies a function to each value. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// ht.Add("banana", 3) -// ht.Add("cherry", 8) -// -// // Function to print each value. -// printValue := func(value int) { -// fmt.Println(value) -// } -// -// // Iterate over the hashtable values and print them. -// ht.EachValue(printValue) -// // Output: 5, 3, 8 +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to print each value. +// printValue := func(value int) { +// fmt.Println(value) +// } +// +// // Iterate over the hashtable values and print them. +// ht.EachValue(printValue) +// // Output: 5, 3, 8 func (hashtable *Hashtable[K, V]) EachValue(fn func(value V)) *Hashtable[K, V] { return hashtable.Each(func(_ K, value V) { fn(value) @@ -256,20 +300,21 @@ func (hashtable *Hashtable[K, V]) EachValue(fn func(value V)) *Hashtable[K, V] { // If the provided function returns false, the iteration breaks early. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// ht.Add("banana", 3) -// ht.Add("cherry", 8) -// -// // Function to process each value. Returns false to break the iteration if the value is 3. -// processValue := func(value int) bool { -// fmt.Println(value) -// return value != 3 -// } -// -// // Iterate over the hashtable values and process them until the value is 3. -// ht.EachValueBreak(processValue) -// // Output: 5, 3 +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Function to process each value. Returns false to break the iteration if the value is 3. +// processValue := func(value int) bool { +// fmt.Println(value) +// return value != 3 +// } +// +// // Iterate over the hashtable values and process them until the value is 3. +// ht.EachValueBreak(processValue) +// // Output: 5, 3 func (hashtable *Hashtable[K, V]) EachValueBreak(fn func(value V) bool) *Hashtable[K, V] { return hashtable.EachBreak(func(_ K, value V) bool { return fn(value) @@ -280,24 +325,75 @@ func (hashtable *Hashtable[K, V]) EachValueBreak(fn func(value V) bool) *Hashtab // If the key exists, it returns the associated value and true. Otherwise, it returns the zero value for the value type and false. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// value, exists := ht.Get("apple") // 5, true -// value, exists = ht.Get("orange") // 0, false +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// value, exists := ht.Get("apple") // 5, true +// value, exists = ht.Get("orange") // 0, false func (hashtable *Hashtable[K, V]) Get(key K) (V, bool) { value, ok := (*hashtable)[key] return value, ok } +// GetMany retrieves values from the hashtable for the specified keys and returns them as a slice. +// If a key is not found in the hashtable, it is skipped in the result slice. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Get values for specific keys. +// values := ht.GetMany("apple", "banana", "orange") +// +// // The resulting values slice: {5, 3} +func (hashtable *Hashtable[K, V]) GetMany(keys ...K) *slice.Slice[V] { + values := &slice.Slice[V]{} + for _, key := range keys { + if value, ok := hashtable.Get(key); ok { + values.Append(value) + } + } + return values +} + // Has checks if the provided key exists in the hashtable. // It returns true if the key exists, and false otherwise. // // Example: -// ht := make(hashtable.Hashtable[string, int]) -// ht.Add("apple", 5) -// exists := ht.Has("apple") // true -// exists = ht.Has("orange") // false +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// exists := ht.Has("apple") // true +// exists = ht.Has("orange") // false func (hashtable *Hashtable[K, V]) Has(key K) bool { _, ok := (*hashtable)[key] return ok } + +// HasMany checks the existence of multiple keys in the hashtable and returns a slice of boolean values +// indicating whether each corresponding key exists in the hashtable. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Check the existence of multiple keys. +// keysToCheck := []string{"apple", "orange", "banana"} +// results := ht.HasMany(keysToCheck...) +// +// // The resulting boolean slice: {true, false, true} +func (hashtable *Hashtable[K, V]) HasMany(keys ...K) *slice.Slice[bool] { + values := make(slice.Slice[bool], len(keys)) + for i, key := range keys { + if hashtable.Has(key) { + values.Replace(i, true) + } + } + return &values +} diff --git a/hashtable_test.go b/hashtable_test.go index 3b67da6..78e6bfc 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -1,10 +1,12 @@ package hashtable_test import ( + "reflect" "sort" "testing" "github.com/lindsaygelle/hashtable" + "github.com/lindsaygelle/slice" ) // TestHashtable tests Hashtable. @@ -16,15 +18,15 @@ func TestHashtable(t *testing.T) { // TestAdd tests Hashtable.Add. func TestAdd(t *testing.T) { - // Create a new hashtable + // Create a new hashtable. ht := make(hashtable.Hashtable[string, int]) - // Add key-value pairs to the hashtable + // Add key-value pairs to the hashtable. ht.Add("apple", 5) ht.Add("banana", 3) ht.Add("cherry", 8) - // Verify that the key-value pairs have been added correctly + // Verify that the key-value pairs have been added correctly. expected := map[string]int{ "apple": 5, "banana": 3, @@ -40,10 +42,10 @@ func TestAdd(t *testing.T) { } } - // Update the value associated with the "banana" key + // Update the value associated with the "banana" key. ht.Add("banana", 10) - // Verify that the value has been updated correctly + // Verify that the value has been updated correctly. if ht["banana"] != 10 { t.Fatalf("Expected value for key 'banana' to be updated to 10, but got %d", ht["banana"]) } @@ -53,60 +55,60 @@ func TestAdd(t *testing.T) { func TestAddFunc(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) - // Test case 1: Add key-value pairs where the value is greater than 1 + // Test case 1: Add key-value pairs where the value is greater than 1. ht.AddFunc([]map[string]int{{"apple": 1, "orange": 2, "banana": 3}}, func(key string, value int) bool { return value > 1 }) - // Check if expected key-value pairs are added + // Check if expected key-value pairs are added. expected := map[string]int{"orange": 2, "banana": 3} for key, expectedValue := range expected { value, exists := ht.Get(key) if !exists || value != expectedValue { - t.Errorf("Expected key '%s' with value '%d', but got value '%d'", key, expectedValue, value) + t.Fatalf("Expected key '%s' with value '%d', but got value '%d'", key, expectedValue, value) } } - // Test case 2: Add all key-value pairs without any validation + // Test case 2: Add all key-value pairs without any validation. ht.AddFunc([]map[string]int{{"kiwi": 0, "pear": 4}}, func(key string, value int) bool { return true }) - // Check if all key-value pairs are added + // Check if all key-value pairs are added. allValues := map[string]int{"orange": 2, "banana": 3, "kiwi": 0, "pear": 4} for key, expectedValue := range allValues { value, exists := ht.Get(key) if !exists || value != expectedValue { - t.Errorf("Expected key '%s' with value '%d', but got value '%d'", key, expectedValue, value) + t.Fatalf("Expected key '%s' with value '%d', but got value '%d'", key, expectedValue, value) } } } -// TestAddMany tests Hashtable.AddMany +// TestAddMany tests Hashtable.AddMany. func TestAddMany(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) - // Test case 1 + // Test case 1. ht.AddMany(map[string]int{"orange": 7, "grape": 4}) expected1 := 7 if val, ok := ht["orange"]; !ok || val != expected1 { - t.Errorf("Expected %d, but got %d for key 'orange'", expected1, val) + t.Fatalf("Expected %d, but got %d for key 'orange'", expected1, val) } expected2 := 4 if val, ok := ht["grape"]; !ok || val != expected2 { - t.Errorf("Expected %d, but got %d for key 'grape'", expected2, val) + t.Fatalf("Expected %d, but got %d for key 'grape'", expected2, val) } - // Test case 2 + // Test case 2. ht.AddMany(map[string]int{"kiwi": 6, "pear": 9}) expected3 := 6 if val, ok := ht["kiwi"]; !ok || val != expected3 { - t.Errorf("Expected %d, but got %d for key 'kiwi'", expected3, val) + t.Fatalf("Expected %d, but got %d for key 'kiwi'", expected3, val) } expected4 := 9 if val, ok := ht["pear"]; !ok || val != expected4 { - t.Errorf("Expected %d, but got %d for key 'pear'", expected4, val) + t.Fatalf("Expected %d, but got %d for key 'pear'", expected4, val) } } @@ -114,40 +116,40 @@ func TestAddMany(t *testing.T) { func TestAddOK(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) - // Test adding a new key-value pair + // 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 + // 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) + t.Fatalf("Expected key 'apple' with value '5', but got value '%d'", value) } - // Test adding an existing key + // 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 + // 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) + t.Fatalf("Expected key 'apple' to have value '5' after re-adding attempt, but got value '%d'", value) } - // Test adding another new key-value pair + // 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 + // 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) + t.Fatalf("Expected key 'banana' with value '3', but got value '%d'", value) } } @@ -157,23 +159,23 @@ func TestDelete(t *testing.T) { ht["apple"] = 5 ht["banana"] = 3 - // Test case 1: Delete an existing key + // Test case 1: Delete an existing key. ht.Delete("apple") if _, ok := ht["apple"]; ok { - t.Errorf("Expected key 'apple' to be deleted, but it still exists in the hashtable") + t.Fatalf("Expected key 'apple' to be deleted, but it still exists in the hashtable") } - // Test case 2: Delete a non-existing key + // Test case 2: Delete a non-existing key. ht.Delete("nonexistent") if _, ok := ht["nonexistent"]; ok { - t.Errorf("Expected key 'nonexistent' to not exist, but it was found in the hashtable") + t.Fatalf("Expected key 'nonexistent' to not exist, but it was found in the hashtable") } - // Test case 3: Delete a key after adding it again + // Test case 3: Delete a key after adding it again. ht["apple"] = 10 ht.Delete("apple") if _, ok := ht["apple"]; ok { - t.Errorf("Expected key 'apple' to be deleted, but it still exists in the hashtable") + t.Fatalf("Expected key 'apple' to be deleted, but it still exists in the hashtable") } } @@ -184,18 +186,18 @@ func TestDeleteFunc(t *testing.T) { ht["orange"] = 7 ht["kiwi"] = 6 - // Delete key-value pairs where the value is 7 + // 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 + // 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 + // 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.") @@ -214,28 +216,45 @@ func TestDeleteMany(t *testing.T) { ht["orange"] = 7 ht["kiwi"] = 6 - // Delete key-value pairs with keys "apple" and "kiwi" + // Delete key-value pairs with keys "apple" and "kiwi". ht.DeleteMany("apple", "kiwi") - // Check if the key-value pair with key "apple" is removed + // 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 + // 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 + // 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.") } } +func TestDeleteManyValues(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht.Add("apple", 5) + ht.Add("banana", 3) + ht.Add("cherry", 8) + + // Delete key-value pairs where the value is 3 or 8. + ht.DeleteManyValues(3, 8) + + // Verify that the hashtable only contains the expected key-value pair. + expected := hashtable.Hashtable[string, int]{"apple": 5} + if !reflect.DeepEqual(ht, expected) { + t.Fatalf("Expected hashtable: %v, but got: %v", expected, ht) + } +} + // TestEach tests Hashtable.Each. func TestEach(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) @@ -256,11 +275,11 @@ func TestEach(t *testing.T) { 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) + t.Fatalf("Expected %s: %d, but got %d", key, expected, value) } delete(expectedOutput, key) } else { - t.Errorf("Unexpected key: %s", key) + t.Fatalf("Unexpected key: %s", key) } } @@ -269,7 +288,7 @@ func TestEach(t *testing.T) { // Check if all expected keys have been processed. if len(expectedOutput) > 0 { - t.Errorf("Not all keys were processed: %v", expectedOutput) + t.Fatalf("Not all keys were processed: %v", expectedOutput) } } @@ -292,7 +311,7 @@ func TestEachBreak(t *testing.T) { // Ensure that iteration stopped at "banana". if stopPrinting != "banana" { - t.Errorf("Iteration did not stop at 'banana'.") + t.Fatalf("Iteration did not stop at 'banana'.") } } @@ -321,7 +340,7 @@ func TestEachKey(t *testing.T) { 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) + t.Fatalf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) } } } @@ -348,11 +367,11 @@ func TestEachKeyBreak(t *testing.T) { // Sort the printed values for consistent comparison. sort.Strings(printedKeys) - // Expected output: "apple", "banana" + // 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) + t.Fatalf("Expected key %s at index %d, but got %s", expectedKeys[i], i, key) } } } @@ -383,13 +402,13 @@ func TestEachValue(t *testing.T) { expectedValues := []int{3, 5, 8} if len(printedValues) != len(expectedValues) { - t.Errorf("Expected %d values, but got %d", len(expectedValues), len(printedValues)) + t.Fatalf("Expected %d values, but got %d", len(expectedValues), len(printedValues)) return } for i, value := range printedValues { if value != expectedValues[i] { - t.Errorf("Expected value %d at index %d, but got %d", expectedValues[i], i, value) + t.Fatalf("Expected value %d at index %d, but got %d", expectedValues[i], i, value) } } } @@ -427,11 +446,11 @@ func TestEachValueBreak(t *testing.T) { } } - // Expected output: 5, 3 + // Expected output: 5, 3. expectedValues := []int{5, 3} for i, value := range processedValues { if value != expectedValues[i] { - t.Errorf("Expected value %d at index %d, but got %d", expectedValues[i], i, value) + t.Fatalf("Expected value %d at index %d, but got %d", expectedValues[i], i, value) } } } @@ -443,39 +462,88 @@ func TestGet(t *testing.T) { ht["apple"] = 5 ht["banana"] = 3 - // Test case 1: Get an existing key + // Test case 1: Get an existing key. value, exists := ht.Get("apple") if !exists { - t.Errorf("Expected key 'apple' to exist, but it was not found in the hashtable") + t.Fatalf("Expected key 'apple' to exist, but it was not found in the hashtable") } if value != 5 { - t.Errorf("Expected value for key 'apple' to be 5, but got %d", value) + t.Fatalf("Expected value for key 'apple' to be 5, but got %d", value) } - // Test case 2: Get a non-existing key + // Test case 2: Get a non-existing key. value, exists = ht.Get("orange") if exists { - t.Errorf("Expected key 'orange' to not exist, but it was found in the hashtable with value %d", value) + t.Fatalf("Expected key 'orange' to not exist, but it was found in the hashtable with value %d", value) } if value != 0 { - t.Errorf("Expected default value for non-existing key 'orange' to be 0, but got %d", value) + t.Fatalf("Expected default value for non-existing key 'orange' to be 0, but got %d", value) } } -// TestHas tests Hashtable.Has +// TestGetMany tests Hashtable.GetMany. +func TestGetMany(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Get values for specific keys. + values := ht.GetMany("apple", "banana", "orange") + + // Sort the keys for consistent iteration order. + sort.Ints(*values) + + // The expected values slice: {5, 3}. + expectedValues := &slice.Slice[int]{5, 3} + + // Sort the keys for consistent iteration order. + sort.Ints(*expectedValues) + + // Verify that the obtained values match the expected values. + if values == expectedValues { + t.Fatalf("Expected values: %v, but got: %v", expectedValues, values) + } +} + +// TestHas tests Hashtable.Has. func TestHas(t *testing.T) { ht := make(hashtable.Hashtable[string, int]) ht["apple"] = 5 ht["banana"] = 3 - // Test case 1: Key exists in the hashtable + // Test case 1: Key exists in the hashtable. if !ht.Has("apple") { - t.Errorf("Expected key 'apple' to exist, but it was not found in the hashtable") + t.Fatalf("Expected key 'apple' to exist, but it was not found in the hashtable") } - // Test case 2: Key does not exist in the hashtable + // Test case 2: Key does not exist in the hashtable. if ht.Has("orange") { - t.Errorf("Expected key 'orange' to not exist, but it was found in the hashtable") + t.Fatalf("Expected key 'orange' to not exist, but it was found in the hashtable") + } +} + +// TestHasMany test Hashtable.HasMany. +func TestHasMany(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht.Add("apple", 5) + ht.Add("banana", 3) + ht.Add("cherry", 8) + + // Keys to check existence. + keysToCheck := []string{"apple", "orange", "banana"} + + // Check the existence of multiple keys. + results := ht.HasMany(keysToCheck...) + + // The expected boolean slice: {true, false, true} + expectedResults := &slice.Slice[bool]{true, false, true} + + // Verify that the obtained results match the expected results. + if !reflect.DeepEqual(results, expectedResults) { + t.Fatalf("Expected results: %v, but got: %v", expectedResults, results) } } From e722ed1b665aebcf6dd8a3335bb4c59a40991fe7 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Wed, 18 Oct 2023 10:13:44 +1100 Subject: [PATCH 2/3] Dev --- hashtable.go | 44 ++++++++++++++++++++++++++++++++++++ hashtable_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/hashtable.go b/hashtable.go index 35608df..a92f2dc 100644 --- a/hashtable.go +++ b/hashtable.go @@ -397,3 +397,47 @@ func (hashtable *Hashtable[K, V]) HasMany(keys ...K) *slice.Slice[bool] { } return &values } + +// Keys returns a slice containing all the keys present in the hashtable. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Get all keys from the hashtable. +// keys := ht.Keys() // Result: {"apple", "banana", "cherry"} +func (hashtable *Hashtable[K, V]) Keys() *slice.Slice[K] { + keys := make(slice.Slice[K], 0) + hashtable.EachKey(func(key K) { + keys.Append(key) + }) + return &keys +} + +// KeysFunc returns a slice containing the keys from the hashtable for which the provided function returns true. +// The provided function `fn` should accept a key of type `K` and return a boolean value. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Get keys from the hashtable where the key length is greater than 5. +// keys := ht.KeysFunc(func(key string) bool { +// return len(key) > 5 +// }) +// // Result: {"banana"} +func (hashtable *Hashtable[K, V]) KeysFunc(fn func(key K) bool) *slice.Slice[K] { + keys := make(slice.Slice[K], 0) + hashtable.EachKey(func(key K) { + if fn(key) { + keys.Append(key) + } + }) + return &keys +} diff --git a/hashtable_test.go b/hashtable_test.go index 78e6bfc..5c3a3f8 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -3,6 +3,7 @@ package hashtable_test import ( "reflect" "sort" + "strings" "testing" "github.com/lindsaygelle/hashtable" @@ -424,11 +425,12 @@ func TestEachValueBreak(t *testing.T) { ht.Add("banana", 3) ht.Add("cherry", 8) - // Sort the keys for consistent iteration order. keys := make([]string, 0, len(ht)) for key := range ht { keys = append(keys, key) } + + // Sort the keys for consistent iteration order. sort.Strings(keys) // Define a function to process each value. It returns false to break the iteration if the value is 3. @@ -529,9 +531,9 @@ func TestHas(t *testing.T) { func TestHasMany(t *testing.T) { // Create a new hashtable. ht := make(hashtable.Hashtable[string, int]) - ht.Add("apple", 5) - ht.Add("banana", 3) - ht.Add("cherry", 8) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 // Keys to check existence. keysToCheck := []string{"apple", "orange", "banana"} @@ -547,3 +549,50 @@ func TestHasMany(t *testing.T) { t.Fatalf("Expected results: %v, but got: %v", expectedResults, results) } } + +// TestKeys tests Hashtable.Keys. +func TestKeys(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Get all keys from the hashtable. + keys := ht.Keys() + + // Sort the keys for consistent iteration order. + sort.Strings(*keys) + + // The expected keys slice: {"apple", "banana", "cherry"} + expectedKeys := &slice.Slice[string]{"apple", "banana", "cherry"} + + // Sort the keys for consistent iteration order. + sort.Strings(*expectedKeys) + + // Verify that the obtained keys match the expected keys. + if !reflect.DeepEqual(keys, expectedKeys) { + t.Errorf("Expected keys: %v, but got: %v", expectedKeys, keys) + } +} + +func TestKeysFunc(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Get keys from the hashtable where the key length is greater than 5. + keys := ht.KeysFunc(func(key string) bool { + return strings.HasPrefix(key, "b") + }) + + // The expected keys slice: {"banana"} + expectedKeys := &slice.Slice[string]{"banana"} + + // Verify that the obtained keys match the expected keys. + if !reflect.DeepEqual(keys, expectedKeys) { + t.Errorf("Expected keys: %v, but got: %v", expectedKeys, keys) + } +} From e144b9020b2bfbf77bb93f26221284246369ec67 Mon Sep 17 00:00:00 2001 From: lindsaygelle Date: Wed, 18 Oct 2023 10:57:40 +1100 Subject: [PATCH 3/3] Dev --- hashtable.go | 39 +++++++++++++++++++++++++++++++++++++++ hashtable_test.go | 21 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/hashtable.go b/hashtable.go index a92f2dc..5254665 100644 --- a/hashtable.go +++ b/hashtable.go @@ -441,3 +441,42 @@ func (hashtable *Hashtable[K, V]) KeysFunc(fn func(key K) bool) *slice.Slice[K] }) return &keys } + +// Length returns the number of key-value pairs in the hashtable. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// length := ht.Length() // Result: 3 +func (hashtable *Hashtable[K, V]) Length() int { + return len(*hashtable) +} + +// Map applies a given function to all key-value pairs in the hashtable and returns a new hashtable with the transformed values. +// The original hashtable remains unchanged. +// +// Example: +// +// ht := make(hashtable.Hashtable[string, int]) +// ht.Add("apple", 5) +// ht.Add("banana", 3) +// ht.Add("cherry", 8) +// +// // Define a function to double the values. +// doubleValue := func(key string, value int) int { +// return value * 2 +// } +// +// // Apply the function to double the values in the hashtable. +// doubledHT := ht.Map(doubleValue) +// // doubledHT contains: {"apple": 10, "banana": 6, "cherry": 16} +func (hashtable *Hashtable[K, V]) Map(fn func(key K, value V) V) *Hashtable[K, V] { + for key, value := range *hashtable { + hashtable.Add(key, fn(key, value)) + } + return hashtable +} diff --git a/hashtable_test.go b/hashtable_test.go index 5c3a3f8..a7b035a 100644 --- a/hashtable_test.go +++ b/hashtable_test.go @@ -576,6 +576,7 @@ func TestKeys(t *testing.T) { } } +// TestKeysFunc tests Hashtable.KeysFunc. func TestKeysFunc(t *testing.T) { // Create a new hashtable. ht := make(hashtable.Hashtable[string, int]) @@ -596,3 +597,23 @@ func TestKeysFunc(t *testing.T) { t.Errorf("Expected keys: %v, but got: %v", expectedKeys, keys) } } + +// TestLength tests Hashtable.Length. +func TestLength(t *testing.T) { + // Create a new hashtable. + ht := make(hashtable.Hashtable[string, int]) + ht["apple"] = 5 + ht["banana"] = 3 + ht["cherry"] = 8 + + // Get the length of the hashtable. + length := ht.Length() + + // Expected length: 3 + expectedLength := 3 + + // Verify that the obtained length matches the expected length. + if length != expectedLength { + t.Errorf("Expected length: %d, but got: %d", expectedLength, length) + } +}