diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..62fc154 --- /dev/null +++ b/example_test.go @@ -0,0 +1,37 @@ +package ordered_sync_map_test + +import ( + "fmt" + + ordered_sync_map "github.com/m-murad/ordered-sync-map" +) + +func ExampleNew() { + + mp := ordered_sync_map.New[string, string]() + + mp.Put("k1", "v1") + + v, ok := mp.Get("k1") + fmt.Println(v, ok) + + ok = mp.Delete("k2") + fmt.Println(ok) + + mp.UnorderedRange(func(key, value string) { + fmt.Println(key, value) + }) + + mp.OrderedRange(func(key, value string) { + fmt.Println(key, value) + }) + + len := mp.Length() + fmt.Println(len) + + v, ok = mp.GetOrPut("k1", "v2") + fmt.Println(v, ok) + + v, ok = mp.GetAndDelete("k1") + fmt.Println(v, ok) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9a4da2b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/m-murad/ordered-sync-map + +go 1.20 diff --git a/map.go b/map.go index a9fe478..ebd5306 100644 --- a/map.go +++ b/map.go @@ -5,59 +5,61 @@ import ( "sync" ) -type mapElement struct { - key interface{} - value interface{} +type mapElement[K comparable, V any] struct { + key K + value V } // Map is a thread safe and ordered implementation of standard map. -type Map struct { - mp map[interface{}]*list.Element +// K is the type of key and V is the type of value. +type Map[K comparable, V any] struct { + mp map[K]*list.Element mu sync.RWMutex dll *list.List } -// New returns an initialized Map. -func New() *Map { - m := new(Map) - m.mp = make(map[interface{}]*list.Element) +// New returns an initialized Map[K, V]. +func New[K comparable, V any]() *Map[K, V] { + m := new(Map[K, V]) + m.mp = make(map[K]*list.Element) m.dll = list.New() return m } -// Get returns the value stored in the map for a key, or nil if no -// value is present. -// The ok result indicates whether value was found in the map. -func (m *Map) Get(key interface{}) (interface{}, bool) { +// Get returns the value stored in the map for a key. +// If the key is not found in the Map it return the zero value of type V. +// The bool indicates whether value was found in the map. +func (m *Map[K, V]) Get(key K) (V, bool) { m.mu.RLock() defer m.mu.RUnlock() v, ok := m.mp[key] if !ok { - return nil, false + var value V + return value, ok } - me := v.Value.(mapElement) + me := v.Value.(mapElement[K, V]) return me.value, ok } // Put sets the value for the given key. // It will replace the value if the key already exists in the map // even if the values are same. -func (m *Map) Put(key interface{}, val interface{}) { +func (m *Map[K, V]) Put(key K, val V) { m.mu.Lock() defer m.mu.Unlock() if e, ok := m.mp[key]; !ok { - m.mp[key] = m.dll.PushFront(mapElement{key: key, value: val}) + m.mp[key] = m.dll.PushFront(mapElement[K, V]{key: key, value: val}) } else { - e.Value = mapElement{key: key, value: val} + e.Value = mapElement[K, V]{key: key, value: val} } } // Delete deletes the value for a key. // It returns a boolean indicating weather the key existed and it was deleted. -func (m *Map) Delete(key interface{}) bool { +func (m *Map[K, V]) Delete(key K) bool { m.mu.Lock() defer m.mu.Unlock() @@ -75,35 +77,32 @@ func (m *Map) Delete(key interface{}) bool { // This is same as ranging over a map using the "for range" syntax. // Parameter func f should not call any method of the Map, eg Get, Put, Delete, UnorderedRange, OrderedRange etc // It will cause a deadlock. -func (m *Map) UnorderedRange(f func(key interface{}, value interface{})) { +func (m *Map[K, V]) UnorderedRange(f func(key K, value V)) { m.mu.RLock() defer m.mu.RUnlock() for k, v := range m.mp { - f(k, v.Value.(mapElement).value) + f(k, v.Value.(mapElement[K, V]).value) } } // OrderedRange will range over the map in ab ordered sequence. -// This is way faster than UnorderedRange. For a map containing 10_000_000 items -// UnorderedRange completes in ~1.7 seconds, -// OrderedRange completes in ~98 milli seconds. // Parameter func f should not call any method of the Map, eg Get, Put, Delete, UnorderedRange, OrderedRange etc // It will cause a deadlock. -func (m *Map) OrderedRange(f func(key interface{}, value interface{})) { +func (m *Map[K, V]) OrderedRange(f func(key K, value V)) { m.mu.RLock() defer m.mu.RUnlock() cur := m.dll.Back() for cur != nil { - me := cur.Value.(mapElement) + me := cur.Value.(mapElement[K, V]) f(me.key, me.value) cur = cur.Prev() } } // Length will return the length of Map. -func (m *Map) Length() int { +func (m *Map[k, V]) Length() int { m.mu.RLock() defer m.mu.RUnlock() @@ -114,15 +113,15 @@ func (m *Map) Length() int { // If the key did not exist previously it will be added to the Map. // updated will be true if the key existed previously // otherwise it will be false if the key did not exist and was added to the Map. -func (m *Map) GetOrPut(key interface{}, value interface{}) (finalValue interface{}, updated bool) { +func (m *Map[K, V]) GetOrPut(key K, value V) (V, bool) { m.mu.Lock() defer m.mu.Unlock() if e, exists := m.mp[key]; exists { - e.Value = mapElement{key: key, value: value} - return value, true + me := e.Value.(mapElement[K, V]) + return me.value, true } else { - m.mp[key] = m.dll.PushFront(mapElement{key: key, value: value}) + m.mp[key] = m.dll.PushFront(mapElement[K, V]{key: key, value: value}) return value, false } } @@ -130,15 +129,17 @@ func (m *Map) GetOrPut(key interface{}, value interface{}) (finalValue interface // GetAndDelete will get the value saved against the given key. // deleted will be true if the key existed previously // otherwise it will be false. -func (m *Map) GetAndDelete(key interface{}) (value interface{}, deleted bool) { +func (m *Map[K, V]) GetAndDelete(key K) (V, bool) { m.mu.Lock() defer m.mu.Unlock() if e, exists := m.mp[key]; exists { m.dll.Remove(e) delete(m.mp, key) - return e.Value, true + me := e.Value.(mapElement[K, V]) + return me.value, true } else { - return nil, false + var value V + return value, false } } diff --git a/map_benchmark_test.go b/map_benchmark_test.go index f2e3879..19d0503 100644 --- a/map_benchmark_test.go +++ b/map_benchmark_test.go @@ -2,17 +2,18 @@ package ordered_sync_map_test import ( "fmt" - mp "github.com/m-murad/ordered-sync-map" "testing" + + mp "github.com/m-murad/ordered-sync-map" ) -func getPopulatedOrderedSyncMap(size int) *mp.Map { - m := mp.New() +func getPopulatedOrderedSyncMap(size int) *mp.Map[any, any] { + m := mp.New[any, any]() populateOrderedSyncMap(m, size) return m } -func populateOrderedSyncMap(m *mp.Map, size int) { +func populateOrderedSyncMap(m *mp.Map[any, any], size int) { for i := 0; i < size; i++ { m.Put(i, i) } @@ -33,7 +34,7 @@ func BenchmarkOrderedSyncMapGet(b *testing.B) { func BenchmarkOrderedSyncMapPut(b *testing.B) { for n := 1; n <= 10; n++ { - m := mp.New() + m := mp.New[any, any]() b.Run(fmt.Sprintf("Put in ordered_sync_map - %d", n), func(b *testing.B) { populateOrderedSyncMap(m, b.N) }) diff --git a/map_test.go b/map_test.go index 3c33706..3f8a668 100644 --- a/map_test.go +++ b/map_test.go @@ -1,12 +1,13 @@ package ordered_sync_map_test import ( - mp "github.com/m-murad/ordered-sync-map" "testing" + + mp "github.com/m-murad/ordered-sync-map" ) -func initMap() *mp.Map { - return mp.New() +func initMap() *mp.Map[any, any] { + return mp.New[any, any]() } func TestGetPutDelete(t *testing.T) { @@ -102,8 +103,8 @@ func TestOrderedRange(t *testing.T) { {123, "key", "val 1", "val_2", true}, //values } - m := mp.New() - for i, _ := range kvs[0] { + m := initMap() + for i := range kvs[0] { m.Put(kvs[0][i], kvs[1][i]) } @@ -123,91 +124,48 @@ func TestOrderedRange(t *testing.T) { func TestLength(t *testing.T) { m := initMap() - m.Put("a", 1) - m.Put("b", 2) - if m.Length() != 2 { - t.FailNow() - } - - m.Put("c", 2) - m.Put("d", 2) - m.Put("e", 2) - if m.Length() != 5 { + if m.Length() != 0 { t.FailNow() } - m.Put("e", 3) - if m.Length() != 5 { + m.Put("a", 1) + m.Put("b", 2) + if m.Length() != 2 { t.FailNow() } m.Delete("a") - if m.Length() != 4 { + if m.Length() != 1 { t.FailNow() } m.Delete("does_not_exist") - if m.Length() != 4 { - t.FailNow() - } - - m.Delete("b") - m.Delete("c") - m.Delete("d") if m.Length() != 1 { t.FailNow() } - - m.Delete("e") - if m.Length() != 0 { - t.FailNow() - } } func TestGetOrPut(t *testing.T) { m := initMap() - m.Put("a", 1) - m.Put("b", 2) - m.Put("c", 3) - - if finalValue, updated := m.GetOrPut("a", 4); finalValue != 1 && !updated { - t.FailNow() - } - - if finalValue, updated := m.GetOrPut("d", 4); finalValue != 4 && updated { - t.FailNow() - } - - m.Delete("a") - if finalValue, updated := m.GetOrPut("a", 5); finalValue != 5 && updated { - t.FailNow() + if finalValue, updated := m.GetOrPut("a", 5); finalValue != 5 || updated { + t.Fail() } - m.Put("e", 5) - if finalValue, updated := m.GetOrPut("e", 6); finalValue != 5 && !updated { - t.FailNow() + if finalValue, updated := m.GetOrPut("a", 4); finalValue != 5 || !updated { + t.Fail() } } func TestGetAndDelete(t *testing.T) { m := initMap() - m.Put("a", 1) - m.Put("b", 2) - m.Put("c", 3) - m.Put("d", 4) - - if value, deleted := m.GetAndDelete("a"); value != 1 && !deleted { - t.Fail() - } - - if value, deleted := m.GetAndDelete("a"); value != nil && deleted { + if value, deleted := m.GetAndDelete("a"); value != nil || deleted { t.Fail() } - m.Put("a", 5) - if value, deleted := m.GetAndDelete("a"); value != 5 && !deleted { + m.Put("a", 2) + if value, deleted := m.GetAndDelete("a"); value != 2 || !deleted { t.Fail() } } diff --git a/readme.md b/readme.md index b6f8363..e8cd939 100644 --- a/readme.md +++ b/readme.md @@ -3,49 +3,4 @@ ordered-sync-map is a package that implements a goroutine-safe ordered map. ### usage - -``` -import ( - "log" - mp "github.com/m-murad/ordered-sync-map" -) - -var m *mp.Map - -func main() { - // Initialise a new thread-safe ordered Map. - m = mp.New() - - // Insert element in the Map. - m.Put(key, value) - - // Get will retrive the value for a key in the Map. - val, ok := m.Get(key) - - // Delete will delete an entry form the Map. - existed := m.Delete(key) - - // UnorderedRange will iterate over the Map in a random sequence. - // This is same as ranging over a map using the "for range" syntax. - // Parameter function should not call any methods associated with the map. - m.UnorderedRange(func(key interface{}, val interface{}) { - log.Println("Key - %v, Value - %v", key, val) - }) - - // OrderedRange will iterate over the Map in the sequence in which - // elements were added. - // Parameter function should not call any methods associated with the map. - m.OrderedRange(func(key interface{}, val interface{}) { - log.Println("Key - %v, Value - %v", key, val) - }) - - // Length will return the length of the Map. - m.Length() - - // GetOrPut will get the currect value if the key exists else it will save the new value - finalValue, updated := m.GetOrPut(key, value) - - // GetAndDelete will get the value for the give key and deleted the key from the Map - value, deleted := m.GetAndDelete(key) -} -``` +Chek the documentation [here](https://pkg.go.dev/github.com/m-murad/ordered-sync-map).