genericmap

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2025 License: Apache-2.0 Imports: 2 Imported by: 0

README ΒΆ

GenericMap

Go Version Test Coverage License Go Report Card

A high-performance, thread-safe, generic bidirectional map implementation in Go with O(1) operations and comprehensive reverse lookup capabilities.

✨ Features

  • πŸš€ High Performance: O(1) set, get, and remove operations
  • πŸ”„ Bidirectional Lookup: Efficient forward (keyβ†’value) and reverse (valueβ†’keys) operations
  • πŸ”’ Thread-Safe: Built-in concurrent access support with RWMutex
  • 🎯 Generic: Type-safe with Go generics for any comparable types
  • πŸ“Š Memory Optimized: Reduced allocations and GC pressure
  • πŸ§ͺ Well Tested: 98.5% test coverage with comprehensive benchmarks

πŸš€ Quick Start

Installation
go get github.com/costa92/genericmap
Basic Usage
package main

import (
    "fmt"
    "github.com/costa92/genericmap"
)

func main() {
    // Create a new map
    m := genericmap.New[string, int]()
    
    // Add key-value pairs
    m.Set("apple", 5)
    m.Set("banana", 2)
    m.Set("cherry", 5)
    
    // Get value by key
    if value, exists := m.Get("apple"); exists {
        fmt.Printf("apple: %d\n", value) // apple: 5
    }
    
    // Reverse lookup - find all keys with value 5
    keys := m.GetKeys(5)
    fmt.Printf("Items with value 5: %v\n", keys) // [apple cherry]
    
    // Remove item
    removed := m.Remove("banana")
    fmt.Printf("Removed banana: %t\n", removed) // true
}

πŸ“– API Documentation

Creating Maps
// Create empty map
m := genericmap.New[string, int]()

// Create with initial data
initial := map[string]int{"a": 1, "b": 2}
m := genericmap.New[string, int](initial)

// Create with capacity (performance optimization)
m := genericmap.NewWithCapacity[string, int](1000)
Core Operations
// Set/Update
m.Set("key", 100)

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

// Remove
removed := m.Remove("key")

// Size
size := m.Len()
Bidirectional Lookup
// Get all keys for a specific value
keys := m.GetKeys(100)

// List all keys
allKeys := m.List()

// List all values
allValues := m.Values()
Thread Safety
// Safe for concurrent use
var wg sync.WaitGroup

// Multiple goroutines can safely access the map
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        m.Set(fmt.Sprintf("key-%d", id), id)
        value, _ := m.Get(fmt.Sprintf("key-%d", id))
        keys := m.GetKeys(id)
        fmt.Printf("Goroutine %d: value=%d, keys=%v\n", id, value, keys)
    }(i)
}

wg.Wait()

🏎️ Performance

Benchmark Results (Apple M1)
Operation Time/op Allocs/op B/op
Set 426.2 ns 1 76 B
Get 14.34 ns 0 0 B
GetKeys 771.9 ns 2 904 B
Remove 248.5 ns 0 0 B
Concurrent R/W 142.2 ns 1 32 B
Performance Optimizations
  • O(1) Operations: Set, Get, and Remove operations are constant time
  • Optimized Reverse Lookup: Uses map[V]map[K]struct{} instead of map[V][]K for O(1) removal
  • Reduced Allocations: Minimal memory allocations during operations
  • Single Map Lookup: Eliminates redundant lookups in Set operations

πŸ“‹ Use Cases

User-Group Mapping
// Map user IDs to group names with reverse lookup
userGroups := genericmap.New[int, string]()

userGroups.Set(1001, "admin")
userGroups.Set(1002, "user")
userGroups.Set(1003, "admin")

// Find all admin users
adminUsers := userGroups.GetKeys("admin") // [1001, 1003]
Cache with Reverse Index
// Cache with ability to find keys by cached values
cache := genericmap.New[string, []byte]()

cache.Set("user:123", []byte("user data"))
cache.Set("post:456", []byte("post data"))

// Find all keys containing specific data
keys := cache.GetKeys([]byte("user data")) // ["user:123"]
Configuration Management
// Manage configuration keys with grouping
config := genericmap.New[string, string]()

config.Set("db.host", "production")
config.Set("cache.host", "production")
config.Set("queue.host", "staging")

// Find all production services
prodServices := config.GetKeys("production") // ["db.host", "cache.host"]

πŸ› οΈ Development

Prerequisites
  • Go 1.25 or higher
  • Make (optional, for using Makefile commands)
Building and Testing
# Run all tests
make test

# Run benchmarks
make bench

# Run tests with coverage
make test-coverage

# Format and lint code
make check

# Show all available commands
make help
Project Structure
β”œβ”€β”€ map.go              # Core implementation
β”œβ”€β”€ map_test.go         # Unit tests
β”œβ”€β”€ example_test.go     # Example tests
β”œβ”€β”€ benchmark_test.go   # Performance benchmarks
β”œβ”€β”€ Makefile           # Build automation
β”œβ”€β”€ MAKEFILE.md        # English Make commands documentation
β”œβ”€β”€ MAKEFILE_CN.md     # Chinese Make commands documentation
└── README.md          # This file

πŸ§ͺ Testing

The project maintains high code quality with comprehensive testing:

  • Unit Tests: Complete coverage of all functionality
  • Example Tests: Documented usage examples
  • Benchmark Tests: Performance regression testing
  • Race Tests: Concurrent access validation
  • Coverage: 98.5% test coverage

Run tests:

# Basic tests
go test -v

# With race detection
go test -race -v

# With coverage
go test -cover -v

πŸ”§ Advanced Usage

Custom Types
type UserID int
type GroupName string

userMap := genericmap.New[UserID, GroupName]()
userMap.Set(UserID(1001), GroupName("admin"))
Performance Tuning
// Pre-allocate capacity for better performance
expectedSize := 10000
m := genericmap.NewWithCapacity[string, int](expectedSize)

// Batch operations
for i := 0; i < expectedSize; i++ {
    m.Set(fmt.Sprintf("key-%d", i), i)
}
Error Handling
// Check if key exists before operations
if value, exists := m.Get("key"); exists {
    // Process existing value
    fmt.Printf("Found: %v\n", value)
} else {
    // Handle missing key
    fmt.Println("Key not found")
}

// Check removal success
if removed := m.Remove("key"); removed {
    fmt.Println("Successfully removed")
} else {
    fmt.Println("Key was not present")
}

πŸ“Š Comparison with Alternatives

Feature GenericMap sync.Map Regular map + mutex
Type Safety βœ… Generics ❌ interface{} βœ… Typed
Reverse Lookup βœ… O(1) ❌ Not supported ❌ O(n) scan
Performance ⚑ Optimized 🐌 Interface overhead πŸ’Ύ Memory efficient
Thread Safety βœ… Built-in βœ… Built-in βš™οΈ Manual
API Simplicity βœ… Clean ❌ Complex βœ… Simple

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Workflow
  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Run tests (make dev)
  4. Commit your changes (git commit -am 'Add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request
Code Style
  • Follow Go conventions and best practices
  • Run make fmt to format code
  • Ensure make ci passes before submitting PR
  • Maintain test coverage above 95%

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • Go team for excellent generics implementation
  • Community feedback and contributions
  • Performance optimization techniques from the Go community

πŸ“š Documentation


GenericMap - High-performance bidirectional maps for Go

Documentation ΒΆ

Overview ΒΆ

Package genericmap provides a thread-safe, generic bidirectional map implementation.

The genericmap package offers a highly efficient map with both forward (key->value) and reverse (value->keys) lookup capabilities, designed specifically for Go 1.18+.

Features:

β€’ Thread-safe concurrent access using sync.RWMutex β€’ Generic type support for both keys and values β€’ Efficient O(1) reverse lookups via prebuilt reverse index β€’ Memory optimized with swap-and-truncate removal β€’ Fully compatible with Go idioms

Basic Usage:

import "github.com/costa92/genericmap"

// Create empty map
m := genericmap.New[string, int]()
m.Set("apple", 5)
m.Set("banana", 2)

// Create with initial data
initial := map[string]int{"a": 1, "b": 2}
m := genericmap.New[string, int](initial)

// Forward lookup
value, exists := m.Get("apple")

// Reverse lookup
keys := m.GetKeys(5) // Gets all keys with value 5

// Thread-safe operations
m.Len()     // Get map size
m.List()    // Get all keys
m.Values()  // Get all values
m.Remove("apple")

Concurrent Usage:

The map is safe for concurrent use from multiple goroutines without external locking.
Reader goroutines use read locks, writer goroutines use write locks.

Example:

m := genericmap.New[int, string]()

go func() { m.Set(1, "admin") }()
go func() { fmt.Println(m.Len()) }()
go func() { keys := m.GetKeys("admin") }()

Performance Characteristics:

β€’ Set: O(1) average case for new keys β€’ Get: O(1) guaranteed β€’ GetKeys: O(k) where k is number of keys for value β€’ Remove: O(1) average case β€’ Len: O(1)

Memory Usage:

The map maintains an additional reverse index, which uses O(n) space where n is the number of unique values in the map.

Package genericmap provides a thread-safe, generic bidirectional map implementation. It supports efficient forward lookups (key->value) and reverse lookups (value->keys).

Example ΒΆ
// Create empty map
m := New[string, int]()
fmt.Printf("Empty map length: %d\n", m.Len())

// Create with initial data
initial := map[string]int{"apple": 5, "banana": 2, "cherry": 5}
m = New[string, int](initial)
fmt.Printf("Initialized map length: %d\n", m.Len())

// Sort keys for consistent output
keys := m.GetKeys(5)
sort.Strings(keys)
fmt.Printf("Keys for value 5: %v\n", keys)
Output:
Empty map length: 0
Initialized map length: 3
Keys for value 5: [apple cherry]

Index ΒΆ

Examples ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

This section is empty.

Types ΒΆ

type Map ΒΆ

type Map[K comparable, V comparable] struct {
	// contains filtered or unexported fields
}

Map is a thread-safe, generic map with bidirectional lookup capabilities. It supports both key-to-value and value-to-keys operations efficiently.

Example (BasicUsage) ΒΆ

ExampleBasicUsage demonstrates basic usage of the genericmap package.

// Create a new map
m := New[string, int]()

// Add key-value pairs
m.Set("apple", 5)
m.Set("banana", 2)
m.Set("cherry", 5)

// Retrieve values
if value, exists := m.Get("apple"); exists {
	fmt.Printf("apple has %d items\n", value)
}

// List all keys (sorted for consistent output)
keys := m.List()
sort.Strings(keys)
fmt.Printf("All keys: %v\n", keys)

// List all values (sorted for consistent output)
values := m.Values()
sort.Ints(values)
fmt.Printf("All values: %v\n", values)

// Reverse lookup - find keys for a value (sorted for consistent output)
keysWithValue5 := m.GetKeys(5)
sort.Strings(keysWithValue5)
fmt.Printf("Keys with value 5: %v\n", keysWithValue5)

// Get map size
fmt.Printf("Map contains %d items\n", m.Len())

// Remove an item
removed := m.Remove("banana")
fmt.Printf("Removed banana: %t, new size: %d\n", removed, m.Len())
Output:
apple has 5 items
All keys: [apple banana cherry]
All values: [2 5 5]
Keys with value 5: [apple cherry]
Map contains 3 items
Removed banana: true, new size: 2
Example (Concurrent) ΒΆ

ExampleConcurrent demonstrates thread-safe concurrent usage.

m := New[int, string]()
var wg sync.WaitGroup

// Multiple goroutines writing
wg.Add(5)
for i := 0; i < 5; i++ {
	go func(id int) {
		defer wg.Done()
		m.Set(id, fmt.Sprintf("item-%d", id))
	}(i)
}

// Multiple goroutines reading
wg.Add(5)
for i := 0; i < 5; i++ {
	go func() {
		defer wg.Done()
		for j := 0; j < 10; j++ {
			_ = m.Len()
			_ = m.List()
			_ = m.GetKeys("test")
		}
	}()
}

wg.Wait()
fmt.Printf("Concurrent operations completed: %d items\n", m.Len())
Output:
Concurrent operations completed: 5 items
Example (InitialData) ΒΆ

ExampleInitialData demonstrates creating a map with initial data.

// Create with initial data
initial := map[string]int{
	"apple":  5,
	"banana": 2,
	"cherry": 5,
}
m := New[string, int](initial)

fmt.Printf("Initial data loaded: %d items\n", m.Len())
keys := m.GetKeys(5)
fmt.Printf("Has value 5 with %d keys\n", len(keys))
Output:
Initial data loaded: 3 items
Has value 5 with 2 keys
Example (StringType) ΒΆ

ExampleStringType demonstrates using string keys and values.

m := New[string, string]()
m.Set("user1", "alice@example.com")
m.Set("user2", "bob@example.com")
m.Set("user3", "alice@example.com")

emails := m.GetKeys("alice@example.com")
// Sort to ensure consistent output order
sort.Strings(emails)
fmt.Printf("Users with email alice@example.com: %v\n", emails)
Output:
Users with email alice@example.com: [user1 user3]
Example (UserGroupMapping) ΒΆ

ExampleUserIDMapping demonstrates a mapping users to groups.

// Map user IDs to group names
userGroups := New[int, string]()

userGroups.Set(1001, "admins")
userGroups.Set(1002, "users")
userGroups.Set(1003, "moderators")
userGroups.Set(1004, "users")
userGroups.Set(1005, "admins")

// Find all users in admins group
adminUsers := userGroups.GetKeys("admins")
// Sort to ensure consistent output order
sort.Ints(adminUsers)
fmt.Printf("Admin user IDs: %v\n", adminUsers)

// Count users in each group
fmt.Printf("Total groups: %d\n", len(userGroups.Values()))

// Count unique groups properly
uniqueGroups := make(map[string]bool)
for _, group := range userGroups.Values() {
	uniqueGroups[group] = true
}
fmt.Printf("Unique groups: %d\n", len(uniqueGroups))
Output:
Admin user IDs: [1001 1005]
Total groups: 5
Unique groups: 3

func New ΒΆ

func New[K comparable, V comparable](initialData ...map[K]V) *Map[K, V]

New creates a new generic map with optional initial data.

Examples:

// Create empty map
m := New[string, int]()

// Create with initial data
initial := map[string]int{"a": 1, "b": 2}
m := New[string, int](initial)

func NewWithCapacity ΒΆ

func NewWithCapacity[K comparable, V comparable](capacity int) *Map[K, V]

NewWithCapacity creates a new generic map with specified initial capacity. This can improve performance when the expected size is known in advance.

func (*Map[K, V]) Get ΒΆ

func (m *Map[K, V]) Get(key K) (V, bool)

Get retrieves the value associated with the key. Returns the value and a boolean indicating if the key exists.

func (*Map[K, V]) GetKeys ΒΆ

func (m *Map[K, V]) GetKeys(value V) []K

GetKeys retrieves all keys associated with a given value. Returns a slice of keys that map to the specified value.

func (*Map[K, V]) Len ΒΆ

func (m *Map[K, V]) Len() int

Len returns the number of key-value pairs in the map.

func (*Map[K, V]) List ΒΆ

func (m *Map[K, V]) List() []K

List returns all keys in the map.

func (*Map[K, V]) Remove ΒΆ

func (m *Map[K, V]) Remove(key K) bool

Remove removes a key-value pair from the map. Returns true if the key existed and was removed, false otherwise.

func (*Map[K, V]) Set ΒΆ

func (m *Map[K, V]) Set(key K, value V)

Set adds or updates a key-value pair in the map.

func (*Map[K, V]) String ΒΆ

func (m *Map[K, V]) String() string

String returns a string representation of the map.

func (*Map[K, V]) Values ΒΆ

func (m *Map[K, V]) Values() []V

Values returns all values in the map.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL