cimap

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 25, 2025 License: MIT Imports: 4 Imported by: 2

README

CaseInsensitiveMap

Go Reference Main Actions Status Go Report Card License

CaseInsensitiveMap (cimap) is a Go package that provides a high performance map-like data structure with case-insensitive keys.

Features

  • Case-Insensitive Keys: Keys are treated in a case-insensitive manner, allowing for more flexible key management.
  • Generic Support: The map supports generic types, allowing you to store any type of value.
  • Custom Hashing: You can set a custom hash function for the map.
  • JSON Serialization: The map can be easily serialized and deserialized to and from JSON.
  • Iterators: Provides iterators for keys and key-value pairs.

Installation

You need Golang 1.23.x or above

go get github.com/projectbarks/cimap

Usage & Documentation

Function documentation can be found at GoDocs. Here's a basic example of how to use the CaseInsensitiveMap:

package main

import (
	"fmt"
	"github.com/projectbarks/cimap"
)

func main() {
	// Create a new case-insensitive map
	m := cimap.New[string]()

	// Add some key-value pairs
	m.Add("KeyOne", "Value1")
	m.Add("keytwo", "Value2")

	// Retrieve values
	val, found := m.Get("KEYONE")
	if found {
		fmt.Println("Found:", val)
	} else {
		fmt.Println("Key not found")
	}

	// Iterate over keys
	m.Keys()(func(key string) bool {
		fmt.Println("Key:", key)
		return true
	})

	// Serialize to JSON
	jsonData, _ := m.MarshalJSON()
	fmt.Println("JSON:", string(jsonData))
}

Performance

  • Time per operation: Over 50% speed improvement compared to native case insensitive map.
  • No additional allocations: CIMap uses 0 B/op and 0 allocs/op for Add, Get, Delete, and more. By converting characters to lowercase inline without extra string allocations, CIMap avoids overhead from creating new strings.
          │    sec/op     │   sec/op     vs base                │
Add/16       45.04n ±  9%   20.85n ± 4%  -53.69% (p=0.000 n=10)
Get/16      131.35n ±  6%   59.49n ± 9%  -54.71% (p=0.000 n=10)
Delete/16    66.89n ± 10%   22.39n ± 6%  -66.53% (p=0.000 n=10)
geomean      73.41n         30.28n       -58.75%
          │     B/op      │   B/op     vs base                     │
Add/16        95.50 ± 39%   0.00 ± 0%  -100.00% (p=0.000 n=10)
Get/16        20.00 ±  0%   0.00 ± 0%  -100.00% (p=0.000 n=10)
Delete/16     16.00 ±  0%   0.00 ± 0%  -100.00% (p=0.000 n=10)
geomean       31.26                    ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean
          │   allocs/op   │ allocs/op   vs base                     │
Add/16       1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Get/16       0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Delete/16    0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
geomean                 ²               ?                       ² ³

Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue.

Development

Comparing the benchmark performance stats:

go install golang.org/x/perf/cmd/benchstat@latest
go test -benchmem -run=^$ -bench '^(Benchmark)' cimap -count=10 > bench-all.txt
grep 'Base-' bench-all.txt | sed 's|Base-||g' > bench-old.txt
grep 'CIMap-' bench-all.txt | sed 's|CIMap-||g' > bench-new.txt
benchstat bench-old.txt bench-new.txt

License

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CaseInsensitiveMap

type CaseInsensitiveMap[T any] struct {
	// contains filtered or unexported fields
}

CaseInsensitiveMap is a generic map that performs case-insensitive key comparisons.

It uses a customizable hash function to store keys in an internal map, handling collisions via separate chaining.

func New

func New[T any](size ...int) *CaseInsensitiveMap[T]

New creates and returns a new CaseInsensitiveMap instance.

An optional positive integer can be provided to preallocate the internal map with the given capacity.

m := cimap.New[int](10)
fmt.Println(m.Len()) // Output: 0

func (*CaseInsensitiveMap[T]) Add

func (c *CaseInsensitiveMap[T]) Add(k string, val T)

Add inserts or updates the key-value pair in the map.

The key comparison is case-insensitive, so if a key differing only by case exists, its value will be replaced with the new one.

m := cimap.New[string]()
m.Add("Hello", "World")
m.Add("hello", "Gophers")

func (*CaseInsensitiveMap[T]) Clear

func (c *CaseInsensitiveMap[T]) Clear()

Clear removes all key-value pairs from the map, resetting it to an empty state.

m := cimap.New[string]()
m.Add("x", "y")
m.Clear()
m.Len() // Output: 0

func (*CaseInsensitiveMap[T]) Delete

func (c *CaseInsensitiveMap[T]) Delete(k string)

Delete removes the key-value pair associated with the specified key from the map.

The key comparison is performed in a case-insensitive manner.

m := cimap.New[int]()
m.Add("delete", 123)
m.Delete("DELETE")
m.Get("delete") // Output: false

func (*CaseInsensitiveMap[T]) ForEach

func (c *CaseInsensitiveMap[T]) ForEach(fn func(string, T) bool)

ForEach executes the provided function for each key-value pair in the map.

Iteration stops early if the function returns false. The order of iteration is undefined.

m.ForEach(func(key string, value int) bool {
    fmt.Printf("%s: %d\n", key, value)
    return true
})

func (*CaseInsensitiveMap[T]) Get

func (c *CaseInsensitiveMap[T]) Get(k string) (T, bool)

Get retrieves the value associated with the specified key using a case-insensitive comparison.

It returns the value and a boolean indicating whether the key was found.

If the key is not present, the zero value of T and false are returned.

m := cimap.New[int]()
m.Add("Key", 42)
value, ok := m.Get("key") // Output: 42 true

func (*CaseInsensitiveMap[T]) GetAndDel

func (c *CaseInsensitiveMap[T]) GetAndDel(k string) (T, bool)

GetAndDel retrieves the value associated with the specified key and then removes the key-value pair from the map.

It returns the value and a boolean indicating whether the key was found.

If the key does not exist, the zero value of T and false are returned.

m := cimap.New[string]()
m.Add("temp", "data")
value, ok := m.GetAndDel("temp") // Output: "data" true
value, ok = m.Get("temp") // Output: false

func (*CaseInsensitiveMap[T]) GetOrSet

func (c *CaseInsensitiveMap[T]) GetOrSet(k string, val T) T

GetOrSet retrieves the value associated with the specified key.

If the key is not present, it sets the value to the provided value and returns it. This ensures that the key exists in the map after the call.

m := cimap.New[int]()
v1 := m.GetOrSet("count", 100) // Output: 100
v2 := m.GetOrSet("COUNT", 200) // Output: 100

func (*CaseInsensitiveMap[T]) Iterator

func (c *CaseInsensitiveMap[T]) Iterator() iter.Seq2[string, T]

Iterator returns an iterator over all key-value pairs in the map. The order of iteration is not guaranteed.

m := cimap.New[string]()
m.Add("first", "a")
m.Add("second", "b")
m.Iterator()(func(key string, value string) bool {
    fmt.Printf("%s: %s\n", key, value) // Output: first: a second: b
    return true
})

func (*CaseInsensitiveMap[T]) Keys

func (c *CaseInsensitiveMap[T]) Keys() iter.Seq[string]

Keys returns an iterator over all keys stored in the map. The iteration order is unspecified.

m := cimap.New[int]()
m.Add("One", 1)
m.Add("Two", 2)
m.Keys()(func(key string) bool {
    fmt.Println(key) // Output: One Two
    return true
})

func (*CaseInsensitiveMap[T]) Len

func (c *CaseInsensitiveMap[T]) Len() int

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

m := cimap.New[int]()
m.Add("a", 1)
m.Add("A", 2)
m.Len() // Output: 1

func (*CaseInsensitiveMap[T]) MarshalJSON

func (c *CaseInsensitiveMap[T]) MarshalJSON() ([]byte, error)

func (*CaseInsensitiveMap[T]) SetHasher

func (c *CaseInsensitiveMap[T]) SetHasher(hashString func(string) hash64)

SetHasher sets a custom hash function for computing keys in the map.

The provided hash function is used for all subsequent operations, and the map is rehashed immediately to reflect the new hashing strategy.

WARNING(a1): Don't use this unless you know what you are doing. This function can destroy the performance of this module if not used correctly.

customHasher := func(s string) uint64 {
    return uint64(len(s))
}
m.SetHasher(customHasher)

func (*CaseInsensitiveMap[T]) UnmarshalJSON

func (c *CaseInsensitiveMap[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface.

It decodes JSON data into the map using case-insensitive key handling. Any existing data in the map is cleared before unmarshalling.

data := []byte(`{"Foo": 10, "bar": 20}`)
var m cimap.CaseInsensitiveMap[int]
if err := json.Unmarshal(data, &m); err != nil {
    log.Fatal(err)
}

Jump to

Keyboard shortcuts

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