fastmap

FastMap - A Type-Safe Generic HashMap Implementation in Go

FastMap currently provides an efficient, type-safe HashMap implementation in Go, offering both thread-safe and non-thread-safe variants. Future releases will expand this into a comprehensive utilities package.

Current Feature Implementation Status:

✅ Core HashMap Implementation

✅ Thread-Safe HashMap

✅ AppendableHashMap

✅ Functional Operations

The current version focuses on providing a robust and efficient HashMap implementation. Future releases will expand the package’s capabilities to include additional data structures and utility functions.

Installation

go get github.com/billowdev/fastmap

Features

Usage

Basic Operations (Non-Thread-Safe)

// Initialize
hashMap := fastmap.NewHashMap[string, YourType]()

// Add/Update
hashMap.Put("key", value)

// Get
value, exists := hashMap.Get("key")

// Remove
hashMap.Remove("key")

// Get size
size := hashMap.Size()

// Update existing value
success := hashMap.UpdateValue("key", newValue)

Thread-Safe Operations

// Initialize thread-safe map
safeMap := fastmap.NewThreadSafeHashMap[string, YourType]()

// Basic Operations
safeMap.Put("key", value)
value, exists := safeMap.Get("key")
safeMap.Remove("key")

// Safe iteration
safeMap.ForEach(func(key string, value YourType) error {
    // Process each key-value pair safely
})

// Check existence
if safeMap.Contains("key") {
    // Key exists, safe for concurrent access
}

// Get all keys safely
keys := safeMap.Keys()
for _, key := range keys {
    // Process keys
}

// Get all values safely
values := safeMap.Values()
for _, value := range values {
    // Process values
}

// Clear all entries safely
safeMap.Clear()

// Check if empty
if safeMap.IsEmpty() {
    // Map is empty
}

// Merge two thread-safe maps
otherMap := fastmap.NewThreadSafeHashMap[string, YourType]()
otherMap.Put("other", value)
safeMap.PutAll(otherMap)

// Convert to regular map
regularMap := safeMap.ToMap()

// Create from regular map
traditional := map[string]YourType{"key": value}
safeMap = fastmap.FromThreadSafeMap(traditional)

Thread-Safe Functional Operations

// Filter with thread safety
activeUsers := safeMap.Filter(func(key string, user User) bool {
    return user.Active
})

// Transform with thread safety
processedUsers := safeMap.Map(func(key string, user User) User {
    user.LastProcessed = time.Now()
    return user
})

// Conditional updates with thread safety
if safeMap.UpdateValue("user1", updatedUser) {
    // Update successful
}

Real-World Example (Thread-Safe)

// User management system with concurrent access
type UserSystem struct {
    users *fastmap.ThreadSafeHashMap[string, User]
}

func NewUserSystem() *UserSystem {
    return &UserSystem{
        users: fastmap.NewThreadSafeHashMap[string, User](),
    }
}

// Safe concurrent operations
func (s *UserSystem) AddUser(id string, user User) {
    s.users.Put(id, user)
}

func (s *UserSystem) GetActiveUsers() []User {
    activeUsers := s.users.Filter(func(id string, user User) bool {
        return user.Active && !user.Deleted
    })
    return activeUsers.Values()
}

func (s *UserSystem) UpdateUserStatus(id string, active bool) bool {
    if user, exists := s.users.Get(id); exists {
        user.Active = active
        return s.users.UpdateValue(id, user)
    }
    return false
}

func (s *UserSystem) ProcessUsers() {
    s.users.ForEach(func(id string, user User) error {
        // Safe concurrent processing
        log.Printf("Processing user: %s", id)
        return nil
    })
}

// Usage in concurrent environment
func main() {
    system := NewUserSystem()
    
    // Concurrent operations
    go func() {
        system.AddUser("1", User{Name: "John", Active: true})
    }()
    
    go func() {
        system.UpdateUserStatus("1", false)
    }()
    
    go func() {
        activeUsers := system.GetActiveUsers()
        for _, user := range activeUsers {
            log.Printf("Active user: %s", user.Name)
        }
    }()
}

import ( “log” “sync” “time”

"github.com/billowdev/fastmap/hashmap" )

// PDFProcessor represents a system that processes PDF components concurrently type PDFProcessor struct { layoutMap *fastmap.ThreadSafeAppendableHashMap[string, PDFComponent] wg sync.WaitGroup }

type PDFComponent struct { ID string Content string Timestamp time.Time }

// NewPDFProcessor initializes a new PDF processor with thread-safe storage func NewPDFProcessor() *PDFProcessor { return &PDFProcessor{ layoutMap: fastmap.NewThreadSafeAppendableHashMapstring, PDFComponent, } }

// ProcessSection handles concurrent processing of PDF components for a section func (p *PDFProcessor) ProcessSection(sectionID string, components []PDFComponent) { batchSize := 5 for i := 0; i < len(components); i += batchSize { end := i + batchSize if end > len(components) { end = len(components) }

    batch := components[i:end]
    p.wg.Add(1)
    go p.processBatch(sectionID, batch)
} }

// processBatch handles a batch of components concurrently func (p *PDFProcessor) processBatch(sectionID string, components []PDFComponent) { defer p.wg.Done()

// Simulate processing time for each component
for _, component := range components {
    // Simulate some processing work
    time.Sleep(100 * time.Millisecond)
    
    // Safely append the processed component
    p.layoutMap.AppendValues(sectionID, component)
    log.Printf("Processed component %s for section %s", component.ID, sectionID)
} }

// GetProcessedComponents safely retrieves all components for a section func (p *PDFProcessor) GetProcessedComponents(sectionID string) []PDFComponent { components, exists := p.layoutMap.Get(sectionID) if !exists { return []PDFComponent{} } return components }

// WaitForCompletion waits for all processing to complete func (p *PDFProcessor) WaitForCompletion() { p.wg.Wait() }

// Usage example func main() { processor := NewPDFProcessor()

// Simulate incoming PDF components for multiple sections
sections := map[string][]PDFComponent{
    "section1": generateComponents("section1", 15),
    "section2": generateComponents("section2", 10),
    "section3": generateComponents("section3", 20),
}

// Process sections concurrently
startTime := time.Now()
for sectionID, components := range sections {
    processor.ProcessSection(sectionID, components)
}

// Wait for all processing to complete
processor.WaitForCompletion()
log.Printf("Processing completed in %v", time.Since(startTime))

// Verify results
for sectionID := range sections {
    processed := processor.GetProcessedComponents(sectionID)
    log.Printf("Section %s has %d processed components", sectionID, len(processed))
} }

// generateComponents creates test PDF components func generateComponents(sectionID string, count int) []PDFComponent { components := make([]PDFComponent, count) for i := 0; i < count; i++ { components[i] = PDFComponent{ ID: fmt.Sprintf(“%s-comp%d”, sectionID, i), Content: fmt.Sprintf(“Content %d”, i), Timestamp: time.Now(), } } return components }

// Example of error handling and recovery func (p *PDFProcessor) ProcessSectionWithRecovery(sectionID string, components []PDFComponent) error { errorChan := make(chan error, 1)

go func() {
    defer func() {
        if r := recover(); r != nil {
            errorChan <- fmt.Errorf("processing panic: %v", r)
        }
        close(errorChan)
    }()

    p.ProcessSection(sectionID, components)
}()

// Wait for completion or error
p.wg.Wait()
if err := <-errorChan; err != nil {
    return fmt.Errorf("section %s processing failed: %w", sectionID, err)
}

return nil } ```

Config Field Processing

The fastmap package provides robust field configuration processing capabilities for handling dynamic data transformations. This feature is particularly useful when dealing with structured data that needs type-safe conversion and validation.

Basic Usage

// Initialize HashMap
hashMap := fastmap.NewHashMap[string, int]()
hashMap.Put("age", 0) // Initialize field

// Define field configurations
configs := map[string]fastmap.FieldConfig[int]{
    "age": {
        Handler: func(data map[string]interface{}) *int {
            if val, ok := data["age"].(float64); ok {
                intVal := int(val)
                return &intVal
            }
            return nil
        },
    },
}

// Process data
data := []map[string]interface{}{
    {"age": 25.0},
    {"age": 30.0},
}

// Method 1: Handle single field
results := hashMap.HandleFieldConfigs(data, configs, "age")
// results = []int{25, 30}

// Method 2: Apply single config
success := hashMap.ApplyFieldConfig("age", configs["age"], data[0])
// success = true, hashMap["age"] = 25

// Method 3: Process all configs with callback
hashMap.ProcessFieldConfigs(configs, data, func(key string, value int, index int) {
    fmt.Printf("Processed %s: %d at index %d\n", key, value, index)
})

Thread-Safe Processing

  1. Basic Thread-Safe Operations ```go // Initialize thread-safe map safeMap := fastmap.NewThreadSafeHashMapstring, float64 safeMap.Put(“temperature”, 0.0)

// Configure field handlers configs := map[string]fastmap.FieldConfig[float64]{ “temperature”: { Handler: func(data map[string]interface{}) *float64 { if val, ok := data[“temp”].(float64); ok { return &val } return nil }, }, }

// Safe concurrent processing safeMap.ProcessFieldConfigs(configs, data, func(key string, value float64, index int) { log.Printf(“Temperature reading %f at index %d”, value, index) })


2. Concurrent Data Processing
```go
// Initialize thread-safe map with complex config
type Measurement struct {
    Value     float64
    Timestamp time.Time
    Valid     bool
}

safeMap := fastmap.NewThreadSafeHashMap[string, Measurement]()
rowIndex := 0

configs := map[string]fastmap.FieldConfig[Measurement]{
    "sensor_data": {
        RowIndex: &rowIndex,
        Handler: func(data map[string]interface{}) *Measurement {
            if val, ok := data["value"].(float64); ok {
                return &Measurement{
                    Value:     val,
                    Timestamp: time.Now(),
                    Valid:     val >= 0 && val <= 100,
                }
            }
            return nil
        },
    },
}

// Concurrent processing with error handling
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(index int) {
        defer wg.Done()
        sensorData := []map[string]interface{}{
            {"value": float64(index * 10)},
        }
        safeMap.ProcessFieldConfigs(configs, sensorData, func(key string, m Measurement, idx int) {
            if m.Valid {
                log.Printf("Valid measurement %f at time %v", m.Value, m.Timestamp)
            }
        })
    }(i)
}
wg.Wait()
  1. Batch Processing with Multiple Fields ```go type ProductData struct { Price float64 Quantity int Total float64 }

safeMap := fastmap.NewThreadSafeHashMapstring, ProductData configs := map[string]fastmap.FieldConfig[ProductData]{ “product”: { Handler: func(data map[string]interface{}) *ProductData { price, ok1 := data[“price”].(float64) qty, ok2 := data[“quantity”].(float64) if !ok1 || !ok2 { return nil } return &ProductData{ Price: price, Quantity: int(qty), Total: price * float64(int(qty)), } }, }, }

// Process batch data concurrently batchData := []map[string]interface{}{ {“price”: 10.5, “quantity”: 2.0}, {“price”: 20.0, “quantity”: 3.0}, }

safeMap.ProcessFieldConfigs(configs, batchData, func(key string, pd ProductData, index int) { log.Printf(“Processed product at index %d: Total = %.2f”, index, pd.Total) })


4. Error Handling in Thread-Safe Context
```go
safeMap := fastmap.NewThreadSafeHashMap[string, int]()
rowIndex := 0

configs := map[string]fastmap.FieldConfig[int]{
    "quantity": {
        RowIndex: &rowIndex,
        Handler: func(data map[string]interface{}) *int {
            val, ok := data["quantity"]
            if !ok {
                log.Printf("Missing quantity field at row %d", *rowIndex)
                return nil
            }
            if floatVal, ok := val.(float64); ok {
                intVal := int(floatVal)
                if intVal < 0 {
                    log.Printf("Invalid negative quantity at row %d", *rowIndex)
                    return nil
                }
                return &intVal
            }
            return nil
        },
    },
}

// Process with validation
safeMap.ProcessFieldConfigs(configs, data, func(key string, quantity int, index int) {
    log.Printf("Processed quantity %d at index %d", quantity, index)
})

Advanced Features

  1. Row Index Tracking
    rowIndex := 0
    configs := map[string]fastmap.FieldConfig[string]{
     "name": {
         RowIndex: &rowIndex,
         Handler: func(data map[string]interface{}) *string {
             if val, ok := data["name"].(string); ok {
                 return &val
             }
             return nil
         },
     },
    }
    
  2. Complex Type Handling ```go type UserData struct { Name string Age int Score float64 }

configs := map[string]fastmap.FieldConfig[UserData]{ “user_info”: { Handler: func(data map[string]interface{}) *UserData { if name, ok := data[“name”].(string); ok { if age, ok := data[“age”].(float64); ok { if score, ok := data[“score”].(float64); ok { return &UserData{ Name: name, Age: int(age), Score: score, } } } } return nil }, }, }


3. Batch Processing with Multiple Configs
```go
configs := map[string]fastmap.FieldConfig[string]{
    "name": {Handler: nameHandler},
    "email": {Handler: emailHandler},
    "phone": {Handler: phoneHandler},
}

hashMap.ProcessFieldConfigs(configs, bulkData, func(key string, value string, index int) {
    switch key {
    case "name":
        processName(value, index)
    case "email":
        processEmail(value, index)
    case "phone":
        processPhone(value, index)
    }
})
  1. Field Dependencies ```go type ProductSpec struct { Price float64 Quantity int Total float64 }

configs := map[string]fastmap.FieldConfig[ProductSpec]{ “product”: { Handler: func(data map[string]interface{}) *ProductSpec { price, ok1 := data[“price”].(float64) qty, ok2 := data[“quantity”].(float64) if !ok1 || !ok2 { return nil } return &ProductSpec{ Price: price, Quantity: int(qty), Total: price * qty, } }, }, }

## Testing

### Unit Tests

Run all unit tests:
```bash
go test ./...

Run specific test:

go test -run TestHashMap_Put

Run tests with coverage:

go test -cover ./...

Generate coverage report:

go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Key Test Categories

  1. Basic Operations
    func TestHashMap_Put(t *testing.T) {
     h := fastmap.NewHashMap[string, int]()
     h.Put("key", 100)
     if val, exists := h.Get("key"); !exists || val != 100 {
         t.Errorf("Put failed, got (%v, %v), want (100, true)", val, exists)
     }
    }
    
  2. Thread-Safe Operations
    func TestThreadSafeConcurrentOperations(t *testing.T) {
     m := fastmap.NewThreadSafeHashMap[string, int]()
     var wg sync.WaitGroup
     numGoroutines := 100
    
     for i := 0; i < numGoroutines; i++ {
         wg.Add(1)
         go func(val int) {
             defer wg.Done()
             m.Put(fmt.Sprintf("key%d", val), val)
         }(i)
     }
     wg.Wait()
    
     if m.Size() != numGoroutines {
         t.Errorf("Expected size %d, got %d", numGoroutines, m.Size())
     }
    }
    
  3. Functional Operations
    func TestFilter(t *testing.T) {
     h := fastmap.NewHashMap[string, int]()
     h.Put("one", 1)
     h.Put("two", 2)
        
     filtered := h.Filter(func(k string, v int) bool {
         return v%2 == 0
     })
        
     if filtered.Size() != 1 {
         t.Error("Filter failed")
     }
    }
    
  4. Edge Cases
    func TestEdgeCases(t *testing.T) {
     h := fastmap.NewHashMap[string, *string]()
     var nilStr *string
        
     h.Put("nilKey", nilStr)
     if val, exists := h.Get("nilKey"); !exists || val != nil {
         t.Error("Failed to handle nil value")
     }
    }
    

Benchmarks

Run all benchmarks:

go test -bench=. ./...

Run specific benchmark:

go test -bench=BenchmarkHashMapPut

Run benchmarks with memory allocation statistics:

go test -bench=. -benchmem ./...

Key Benchmark Categories

  1. Basic Operations
    func BenchmarkHashMapPut(b *testing.B) {
     h := fastmap.NewHashMap[string, int]()
     for i := 0; i < b.N; i++ {
         h.Put("key", i)
     }
    }
    
  2. Thread-Safe Operations
    func BenchmarkThreadSafePut(b *testing.B) {
     m := fastmap.NewThreadSafeHashMap[string, int]()
     b.RunParallel(func(pb *testing.PB) {
         i := 0
         for pb.Next() {
             m.Put(fmt.Sprintf("key%d", i), i)
             i++
         }
     })
    }
    
  3. Functional Operations
    func BenchmarkHashMapFilter(b *testing.B) {
     h := fastmap.NewHashMap[string, int]()
     for i := 0; i < 1000; i++ {
         h.Put(fmt.Sprintf("key%d", i), i)
     }
     b.ResetTimer()
     for i := 0; i < b.N; i++ {
         h.Filter(func(k string, v int) bool {
             return v%2 == 0
         })
     }
    }
    

Benchmark Tips

  1. Use realistic data sizes
  2. Include parallel benchmarks for thread-safe operations
  3. Test with different data types and structures
  4. Measure memory allocations for memory-sensitive operations
  5. Compare performance with standard library map operations

Common Benchmark Flags

-bench=.                # Run all benchmarks
-benchmem              # Print memory allocation statistics
-benchtime=10s         # Run each benchmark for 10 seconds
-count=5               # Run each benchmark 5 times
-cpu=1,2,4            # Run benchmarks with different GOMAXPROCS values

Profile Benchmarks

Generate CPU profile:

go test -bench=. -cpuprofile=cpu.prof
go tool pprof cpu.prof

Generate memory profile:

go test -bench=. -memprofile=mem.prof
go tool pprof mem.prof

Migration Guide

Traditional Map vs HashMap

// Traditional
traditional := make(map[string]YourType)
traditional[key] = value

// Non-Thread-Safe HashMap
hashMap := fastmap.NewHashMap[string, YourType]()
hashMap.Put(key, value)

// Thread-Safe HashMap
safeMap := fastmap.NewThreadSafeHashMap[string, YourType]()
safeMap.Put(key, value)

Performance

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

Top contributors:

contrib.rocks image

License

This project is licensed under the MIT License