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