Skip to content

Commit 163fb11

Browse files
committed
feat: hashmap
1 parent d5ecf33 commit 163fb11

19 files changed

+2454
-1
lines changed

README.md

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,218 @@
1-
# fastmap
1+
# `fastmap` Generic HashMap Package
2+
3+
A type-safe, generic HashMap implementation in Go with both thread-safe and non-thread-safe variants.
4+
5+
## Installation
6+
7+
```bash
8+
go get github.com/billowdev/fastmap
9+
```
10+
11+
## Features
12+
13+
- Generic type support for keys and values
14+
- Thread-safe implementation available
15+
- Type-safe operations
16+
- Functional programming utilities
17+
- Zero dependencies
18+
- Comprehensive test coverage
19+
20+
## Usage
21+
22+
### Basic Operations (Non-Thread-Safe)
23+
24+
```go
25+
// Initialize
26+
hashMap := fastmap.NewHashMap[string, YourType]()
27+
28+
// Add/Update
29+
hashMap.Put("key", value)
30+
31+
// Get
32+
value, exists := hashMap.Get("key")
33+
34+
// Remove
35+
hashMap.Remove("key")
36+
37+
// Get size
38+
size := hashMap.Size()
39+
40+
// Update existing value
41+
success := hashMap.UpdateValue("key", newValue)
42+
```
43+
44+
### Thread-Safe Operations
45+
46+
```go
47+
// Initialize thread-safe map
48+
safeMap := fastmap.NewThreadSafeHashMap[string, YourType]()
49+
50+
// Basic Operations
51+
safeMap.Put("key", value)
52+
value, exists := safeMap.Get("key")
53+
safeMap.Remove("key")
54+
55+
// Safe iteration
56+
safeMap.ForEach(func(key string, value YourType) {
57+
// Process each key-value pair safely
58+
})
59+
60+
// Check existence
61+
if safeMap.Contains("key") {
62+
// Key exists, safe for concurrent access
63+
}
64+
65+
// Get all keys safely
66+
keys := safeMap.Keys()
67+
for _, key := range keys {
68+
// Process keys
69+
}
70+
71+
// Get all values safely
72+
values := safeMap.Values()
73+
for _, value := range values {
74+
// Process values
75+
}
76+
77+
// Clear all entries safely
78+
safeMap.Clear()
79+
80+
// Check if empty
81+
if safeMap.IsEmpty() {
82+
// Map is empty
83+
}
84+
85+
// Merge two thread-safe maps
86+
otherMap := fastmap.NewThreadSafeHashMap[string, YourType]()
87+
otherMap.Put("other", value)
88+
safeMap.PutAll(otherMap)
89+
90+
// Convert to regular map
91+
regularMap := safeMap.ToMap()
92+
93+
// Create from regular map
94+
traditional := map[string]YourType{"key": value}
95+
safeMap = fastmap.FromThreadSafeMap(traditional)
96+
```
97+
98+
### Thread-Safe Functional Operations
99+
100+
```go
101+
// Filter with thread safety
102+
activeUsers := safeMap.Filter(func(key string, user User) bool {
103+
return user.Active
104+
})
105+
106+
// Transform with thread safety
107+
processedUsers := safeMap.Map(func(key string, user User) User {
108+
user.LastProcessed = time.Now()
109+
return user
110+
})
111+
112+
// Conditional updates with thread safety
113+
if safeMap.UpdateValue("user1", updatedUser) {
114+
// Update successful
115+
}
116+
```
117+
118+
### Real-World Example (Thread-Safe)
119+
120+
```go
121+
// User management system with concurrent access
122+
type UserSystem struct {
123+
users *fastmap.ThreadSafeHashMap[string, User]
124+
}
125+
126+
func NewUserSystem() *UserSystem {
127+
return &UserSystem{
128+
users: fastmap.NewThreadSafeHashMap[string, User](),
129+
}
130+
}
131+
132+
// Safe concurrent operations
133+
func (s *UserSystem) AddUser(id string, user User) {
134+
s.users.Put(id, user)
135+
}
136+
137+
func (s *UserSystem) GetActiveUsers() []User {
138+
activeUsers := s.users.Filter(func(id string, user User) bool {
139+
return user.Active && !user.Deleted
140+
})
141+
return activeUsers.Values()
142+
}
143+
144+
func (s *UserSystem) UpdateUserStatus(id string, active bool) bool {
145+
if user, exists := s.users.Get(id); exists {
146+
user.Active = active
147+
return s.users.UpdateValue(id, user)
148+
}
149+
return false
150+
}
151+
152+
func (s *UserSystem) ProcessUsers() {
153+
s.users.ForEach(func(id string, user User) {
154+
// Safe concurrent processing
155+
log.Printf("Processing user: %s", id)
156+
})
157+
}
158+
159+
// Usage in concurrent environment
160+
func main() {
161+
system := NewUserSystem()
162+
163+
// Concurrent operations
164+
go func() {
165+
system.AddUser("1", User{Name: "John", Active: true})
166+
}()
167+
168+
go func() {
169+
system.UpdateUserStatus("1", false)
170+
}()
171+
172+
go func() {
173+
activeUsers := system.GetActiveUsers()
174+
for _, user := range activeUsers {
175+
log.Printf("Active user: %s", user.Name)
176+
}
177+
}()
178+
}
179+
```
180+
181+
## Migration Guide
182+
183+
### Traditional Map vs HashMap
184+
185+
```go
186+
// Traditional
187+
traditional := make(map[string]YourType)
188+
traditional[key] = value
189+
190+
// Non-Thread-Safe HashMap
191+
hashMap := fastmap.NewHashMap[string, YourType]()
192+
hashMap.Put(key, value)
193+
194+
// Thread-Safe HashMap
195+
safeMap := fastmap.NewThreadSafeHashMap[string, YourType]()
196+
safeMap.Put(key, value)
197+
```
198+
199+
## Performance
200+
201+
- Built on Go's native map implementation
202+
- Minimal method call overhead
203+
- O(1) average case for basic operations
204+
- Thread-safe operations use sync.RWMutex
205+
- Read operations allow concurrent access
206+
- Write operations ensure exclusive access
207+
208+
## Contributing
209+
210+
1. Fork the repository
211+
2. Create your feature branch
212+
3. Commit your changes
213+
4. Push to the branch
214+
5. Create a Pull Request
215+
216+
## License
217+
218+
This project is licensed under the MIT License

conversion.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fastmap
2+
3+
// ToMap returns the underlying map
4+
// Example:
5+
//
6+
// standardMap := hashMap.ToMap()
7+
// for k, v := range standardMap {
8+
// fmt.Printf("%v: %v\n", k, v)
9+
// }
10+
func (h *HashMap[K, V]) ToMap() map[K]V {
11+
result := make(map[K]V, len(h.data))
12+
for k, v := range h.data {
13+
result[k] = v
14+
}
15+
return result
16+
}
17+
18+
// FromMap creates a new HashMap from a regular map
19+
// Example:
20+
//
21+
// regularMap := map[string]int{"one": 1, "two": 2}
22+
// hashMap := FromMap(regularMap)
23+
func FromMap[K comparable, V any](m map[K]V) *HashMap[K, V] {
24+
h := NewHashMap[K, V]()
25+
for k, v := range m {
26+
h.Put(k, v)
27+
}
28+
return h
29+
}

conversion_benchmark_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package fastmap
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func BenchmarkHashMapToMap(b *testing.B) {
9+
h := NewHashMap[string, int]()
10+
for i := 0; i < 1000; i++ {
11+
h.Put(fmt.Sprintf("key%d", i), i)
12+
}
13+
b.ResetTimer()
14+
for i := 0; i < b.N; i++ {
15+
h.ToMap()
16+
}
17+
}
18+
19+
func BenchmarkHashMapFromMap(b *testing.B) {
20+
m := make(map[string]int)
21+
for i := 0; i < 1000; i++ {
22+
m[fmt.Sprintf("key%d", i)] = i
23+
}
24+
b.ResetTimer()
25+
for i := 0; i < b.N; i++ {
26+
FromMap(m)
27+
}
28+
}

conversion_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// conversion_test.go
2+
package fastmap
3+
4+
import (
5+
"fmt"
6+
"reflect"
7+
"sync"
8+
"testing"
9+
)
10+
11+
func TestToMap(t *testing.T) {
12+
h := NewHashMap[string, int]()
13+
h.Put("one", 1)
14+
h.Put("two", 2)
15+
16+
want := map[string]int{"one": 1, "two": 2}
17+
got := h.ToMap()
18+
19+
if !reflect.DeepEqual(got, want) {
20+
t.Errorf("ToMap() = %v, want %v", got, want)
21+
}
22+
}
23+
24+
func TestFromMap(t *testing.T) {
25+
original := map[string]int{"one": 1, "two": 2}
26+
h := FromMap(original)
27+
28+
if h.Size() != len(original) {
29+
t.Errorf("FromMap() size = %v, want %v", h.Size(), len(original))
30+
}
31+
32+
for k, v := range original {
33+
if got, exists := h.Get(k); !exists || got != v {
34+
t.Errorf("FromMap() value for key %v = %v, want %v", k, got, v)
35+
}
36+
}
37+
}
38+
func TestConversionEdgeCases(t *testing.T) {
39+
t.Run("empty map", func(t *testing.T) {
40+
h := FromMap(map[string]int{})
41+
if h.Size() != 0 {
42+
t.Error("FromMap empty map failed")
43+
}
44+
if len(h.ToMap()) != 0 {
45+
t.Error("ToMap empty map failed")
46+
}
47+
})
48+
49+
t.Run("nil map", func(t *testing.T) {
50+
var m map[string]int
51+
h := FromMap(m)
52+
if h.Size() != 0 {
53+
t.Error("FromMap nil map failed")
54+
}
55+
})
56+
57+
t.Run("large map", func(t *testing.T) {
58+
m := make(map[string]int)
59+
for i := 0; i < 10000; i++ {
60+
m[fmt.Sprintf("key%d", i)] = i
61+
}
62+
h := FromMap(m)
63+
if !reflect.DeepEqual(h.ToMap(), m) {
64+
t.Error("Large map conversion failed")
65+
}
66+
})
67+
68+
t.Run("concurrent access", func(t *testing.T) {
69+
var wg sync.WaitGroup
70+
71+
for i := 0; i < 100; i++ {
72+
wg.Add(1)
73+
go func(i int) {
74+
defer wg.Done()
75+
m := map[string]int{fmt.Sprintf("key%d", i): i}
76+
temp := FromMap(m)
77+
temp.ToMap()
78+
}(i)
79+
}
80+
wg.Wait()
81+
})
82+
83+
t.Run("non-string keys", func(t *testing.T) {
84+
m := map[int]string{1: "one", 2: "two"}
85+
h := FromMap(m)
86+
if !reflect.DeepEqual(h.ToMap(), m) {
87+
t.Error("Integer key conversion failed")
88+
}
89+
})
90+
91+
t.Run("pointer values", func(t *testing.T) {
92+
type Data struct {
93+
value int // Changed field name to be used
94+
}
95+
m := map[string]*Data{"a": {1}, "b": {2}}
96+
h := FromMap(m)
97+
convertedMap := h.ToMap()
98+
99+
// Compare values explicitly
100+
for k, v := range m {
101+
if got, exists := convertedMap[k]; !exists || got.value != v.value {
102+
t.Errorf("For key %s, got %v, want %v", k, got, v)
103+
}
104+
}
105+
})
106+
}

0 commit comments

Comments
 (0)