Skip to content

Commit

Permalink
Rewrite the package using Generics and add Module support
Browse files Browse the repository at this point in the history
Signed-off-by: murad <mohammad.murad@mondu.ai>
  • Loading branch information
murad committed Mar 26, 2023
1 parent dbe832a commit 3d1aafc
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 145 deletions.
37 changes: 37 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/m-murad/ordered-sync-map

go 1.20
69 changes: 35 additions & 34 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()

Expand All @@ -114,31 +113,33 @@ 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
}
}

// 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
}
}
11 changes: 6 additions & 5 deletions map_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
})
Expand Down
78 changes: 18 additions & 60 deletions map_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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])
}

Expand All @@ -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()
}
}
Loading

0 comments on commit 3d1aafc

Please sign in to comment.