From acae9a6b4193ee204b915880cb6117106dc43938 Mon Sep 17 00:00:00 2001 From: Denis Nutiu Date: Sun, 15 Sep 2024 15:00:16 +0300 Subject: [PATCH] Implement hash set --- hash_set/hash_set/hash_set.go | 92 ++++++++++++++++++++++++++++++ hash_set/hash_set/hash_set_test.go | 85 +++++++++++++++++++++++++++ hash_set/main.go | 51 +++++++++++++++++ main.go | 25 -------- readme.md | 6 ++ 5 files changed, 234 insertions(+), 25 deletions(-) create mode 100644 hash_set/hash_set/hash_set.go create mode 100644 hash_set/hash_set/hash_set_test.go create mode 100644 hash_set/main.go delete mode 100644 main.go create mode 100644 readme.md diff --git a/hash_set/hash_set/hash_set.go b/hash_set/hash_set/hash_set.go new file mode 100644 index 0000000..e5ebe77 --- /dev/null +++ b/hash_set/hash_set/hash_set.go @@ -0,0 +1,92 @@ +package hash_set + +import ( + "fmt" + "strings" +) + +type MyHash interface { + ~string | ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8 +} + +type Hasher[H MyHash] interface { + Hash() H +} + +// MyHashSet is a hash set implementation. +type MyHashSet[T Hasher[H], H MyHash] struct { + storage map[H]T +} + +// NewSet initializes a new hash set. +func NewSet[T Hasher[H], H MyHash]() MyHashSet[T, H] { + return MyHashSet[T, H]{ + storage: make(map[H]T, 100), + } +} + +// Add adds an element to a set. +func (s *MyHashSet[T, H]) Add(element T) { + // Hash element + hash := element.Hash() + + // Save + _, ok := s.storage[hash] + if !ok { + s.storage[hash] = element + } +} + +// AddAll adds all the elements to the set. +func (s *MyHashSet[T, H]) AddAll(elements ...T) { + for _, element := range elements { + s.Add(element) + } +} + +// Contains checks if the hash set contains the element T. +// Returns true if the element is part of the set, false otherwise. +func (s *MyHashSet[T, H]) Contains(element T) bool { + // Hash element + hash := element.Hash() + _, ok := s.storage[hash] + return ok +} + +// Delete deletes an element from the set. +// Returns true if the element was deleted, false otherwise. +func (s *MyHashSet[T, H]) Delete(element T) bool { + // Hash element + hash := element.Hash() + _, ok := s.storage[hash] + if ok { + delete(s.storage, hash) + return true + } + return false +} + +// String returns the string representation of the set. +func (s *MyHashSet[T, H]) String() string { + var sb strings.Builder + sb.WriteString("MyHashSet{") + for _, value := range s.storage { + sb.WriteString(fmt.Sprintf("%v,", value)) + } + sb.WriteString("}") + return sb.String() +} + +// Union creates a union of two sets +func (s *MyHashSet[T, H]) Union(other *MyHashSet[T, H]) { + for _, v := range other.storage { + s.Add(v) + } +} + +// Sub creates a difference of two sets +func (s *MyHashSet[T, H]) Sub(other *MyHashSet[T, H]) { + for _, v := range other.storage { + s.Delete(v) + } +} diff --git a/hash_set/hash_set/hash_set_test.go b/hash_set/hash_set/hash_set_test.go new file mode 100644 index 0000000..8f5e9cd --- /dev/null +++ b/hash_set/hash_set/hash_set_test.go @@ -0,0 +1,85 @@ +package hash_set + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type MyString struct { + Value string +} + +func (m MyString) Hash() string { + return m.Value +} + +func TestNewSet(t *testing.T) { + newSet := NewSet[MyString, string]() + assert.NotNil(t, newSet) +} + +func TestMyHashSet_Add(t *testing.T) { + // Given + newSet := NewSet[MyString, string]() + newSet.Add(MyString{Value: "some"}) + + // Then + assert.True(t, newSet.Contains(MyString{Value: "some"})) +} + +func TestMyHashSet_AddAll(t *testing.T) { + // Given + newSet := NewSet[MyString, string]() + newSet.AddAll(MyString{Value: "some"}, MyString{Value: "another"}) + + // Then + assert.True(t, newSet.Contains(MyString{Value: "some"})) + assert.True(t, newSet.Contains(MyString{Value: "another"})) +} + +func TestMyHashSet_Delete(t *testing.T) { + // Given + newSet := NewSet[MyString, string]() + newSet.AddAll(MyString{Value: "some"}, MyString{Value: "another"}) + newSet.Delete(MyString{Value: "some"}) + + // Then + assert.False(t, newSet.Contains(MyString{Value: "some"})) + assert.True(t, newSet.Contains(MyString{Value: "another"})) +} + +func TestMyHashSet_Union(t *testing.T) { + // Setup + newSet := NewSet[MyString, string]() + newSet.AddAll(MyString{Value: "some"}, MyString{Value: "another"}) + + anotherSet := NewSet[MyString, string]() + anotherSet.AddAll(MyString{Value: "Batman"}, MyString{Value: "Robin"}) + + // Test + newSet.Union(&anotherSet) + + // Then + assert.True(t, newSet.Contains(MyString{Value: "some"})) + assert.True(t, newSet.Contains(MyString{Value: "another"})) + assert.True(t, newSet.Contains(MyString{Value: "Batman"})) + assert.True(t, newSet.Contains(MyString{Value: "Robin"})) +} + +func TestMyHashSet_Sub(t *testing.T) { + // Setup + newSet := NewSet[MyString, string]() + newSet.AddAll(MyString{Value: "some"}, MyString{Value: "another"}, MyString{Value: "Batman"}) + + anotherSet := NewSet[MyString, string]() + anotherSet.AddAll(MyString{Value: "Batman"}, MyString{Value: "Robin"}) + + // Test + newSet.Sub(&anotherSet) + + // Then + assert.True(t, newSet.Contains(MyString{Value: "some"})) + assert.True(t, newSet.Contains(MyString{Value: "another"})) + assert.False(t, newSet.Contains(MyString{Value: "Batman"})) + assert.False(t, newSet.Contains(MyString{Value: "Robin"})) +} diff --git a/hash_set/main.go b/hash_set/main.go new file mode 100644 index 0000000..21cc479 --- /dev/null +++ b/hash_set/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "go-dsa/hash_set/v2/hash_set" +) + +type Person struct { + Name string + Age int +} + +// Hash returns the has of a person, it conforms to the hash_set.Hasher interface +func (p Person) Hash() string { + return fmt.Sprintf("%s-%d", p.Name, p.Age) +} + +func main() { + mySet := hash_set.NewSet[Person, string]() + + // Add + mySet.Add(Person{ + Name: "Batman", + Age: 28, + }) + mySet.Add(Person{ + Name: "Robin", + Age: 16, + }) + mySet.Add(Person{ + Name: "Batman", + Age: 28, + }) + + // Print + fmt.Printf("%s\n", mySet.String()) + + // Contains + result := mySet.Contains(Person{ + Name: "Batman", + Age: 28, + }) + fmt.Printf("Set contains batman %v\n", result) + + // Deletion + mySet.Delete(Person{ + Name: "Batman", + Age: 28, + }) + fmt.Printf("%s\n", mySet.String()) +} diff --git a/main.go b/main.go deleted file mode 100644 index 91b22d0..0000000 --- a/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "fmt" -) - -//TIP To run your code, right-click the code and select Run. Alternatively, click -// the icon in the gutter and select the Run menu item from here. - -func main() { - //TIP Press when your caret is at the underlined or highlighted text - // to see how GoLand suggests fixing it. - s := "gopher" - fmt.Println("Hello and welcome, %s!", s) - - for i := 1; i <= 5; i++ { - //TIP You can try debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . To start your debugging session, - // right-click your code in the editor and select the Debug option. - fmt.Println("i =", 100/i) - } -} - -//TIP See GoLand help at jetbrains.com/help/go/. -// Also, you can try interactive lessons for GoLand by selecting 'Help | Learn IDE Features' from the main menu. \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..95b4c86 --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ +# Data Structures and Algorithms + +This repository is my self study guide for data structures and algorithms. I implement them from scratch in Go +and then write unit test for them. + +What better way to learn a language and new concepts exists other than practicing them. \ No newline at end of file