From cf1077bec4e384cf8a2ad1b865c9b1661e91a5fb Mon Sep 17 00:00:00 2001 From: John Date: Sun, 12 May 2019 20:43:52 +0800 Subject: [PATCH] add LinkMap for gmap package --- g/container/glist/glist.go | 35 ++ ...ny_any_map.go => gmap_hash_any_any_map.go} | 0 ...nt_any_map.go => gmap_hash_int_any_map.go} | 0 ...nt_int_map.go => gmap_hash_int_int_map.go} | 0 ...nt_str_map.go => gmap_hash_int_str_map.go} | 0 ...tr_any_map.go => gmap_hash_str_any_map.go} | 0 ...tr_int_map.go => gmap_hash_str_int_map.go} | 0 ...tr_str_map.go => gmap_hash_str_str_map.go} | 0 g/container/gmap/gmap_link_map.go | 366 ++++++++++++++++++ g/container/gmap/gmap_z_link_map_test.go | 120 ++++++ 10 files changed, 521 insertions(+) rename g/container/gmap/{gmap_any_any_map.go => gmap_hash_any_any_map.go} (100%) rename g/container/gmap/{gmap_int_any_map.go => gmap_hash_int_any_map.go} (100%) rename g/container/gmap/{gmap_int_int_map.go => gmap_hash_int_int_map.go} (100%) rename g/container/gmap/{gmap_int_str_map.go => gmap_hash_int_str_map.go} (100%) rename g/container/gmap/{gmap_str_any_map.go => gmap_hash_str_any_map.go} (100%) rename g/container/gmap/{gmap_str_int_map.go => gmap_hash_str_int_map.go} (100%) rename g/container/gmap/{gmap_str_str_map.go => gmap_hash_str_str_map.go} (100%) create mode 100644 g/container/gmap/gmap_link_map.go create mode 100644 g/container/gmap/gmap_z_link_map_test.go diff --git a/g/container/glist/glist.go b/g/container/glist/glist.go index c4a1aec23..868136571 100644 --- a/g/container/glist/glist.go +++ b/g/container/glist/glist.go @@ -333,4 +333,39 @@ func (l *List) LockFunc(f func(list *list.List)) { l.mu.Lock() defer l.mu.Unlock() f(l.list) +} + +// Iterator is alias of IteratorAsc. +func (l *List) Iterator(f func (e *Element) bool) { + l.IteratorAsc(f) +} + +// IteratorAsc iterates the list in ascending order with given callback function . +// If returns true, then it continues iterating; or false to stop. +func (l *List) IteratorAsc(f func (e *Element) bool) { + l.mu.RLock() + length := l.list.Len() + if length > 0 { + for i, e := 0, l.list.Front(); i < length; i, e = i + 1, e.Next() { + if !f(e) { + break + } + } + } + l.mu.RUnlock() +} + +// IteratorDesc iterates the list in descending order with given callback function . +// If returns true, then it continues iterating; or false to stop. +func (l *List) IteratorDesc(f func (e *Element) bool) { + l.mu.RLock() + length := l.list.Len() + if length > 0 { + for i, e := 0, l.list.Back(); i < length; i, e = i + 1, e.Prev() { + if !f(e) { + break + } + } + } + l.mu.RUnlock() } \ No newline at end of file diff --git a/g/container/gmap/gmap_any_any_map.go b/g/container/gmap/gmap_hash_any_any_map.go similarity index 100% rename from g/container/gmap/gmap_any_any_map.go rename to g/container/gmap/gmap_hash_any_any_map.go diff --git a/g/container/gmap/gmap_int_any_map.go b/g/container/gmap/gmap_hash_int_any_map.go similarity index 100% rename from g/container/gmap/gmap_int_any_map.go rename to g/container/gmap/gmap_hash_int_any_map.go diff --git a/g/container/gmap/gmap_int_int_map.go b/g/container/gmap/gmap_hash_int_int_map.go similarity index 100% rename from g/container/gmap/gmap_int_int_map.go rename to g/container/gmap/gmap_hash_int_int_map.go diff --git a/g/container/gmap/gmap_int_str_map.go b/g/container/gmap/gmap_hash_int_str_map.go similarity index 100% rename from g/container/gmap/gmap_int_str_map.go rename to g/container/gmap/gmap_hash_int_str_map.go diff --git a/g/container/gmap/gmap_str_any_map.go b/g/container/gmap/gmap_hash_str_any_map.go similarity index 100% rename from g/container/gmap/gmap_str_any_map.go rename to g/container/gmap/gmap_hash_str_any_map.go diff --git a/g/container/gmap/gmap_str_int_map.go b/g/container/gmap/gmap_hash_str_int_map.go similarity index 100% rename from g/container/gmap/gmap_str_int_map.go rename to g/container/gmap/gmap_hash_str_int_map.go diff --git a/g/container/gmap/gmap_str_str_map.go b/g/container/gmap/gmap_hash_str_str_map.go similarity index 100% rename from g/container/gmap/gmap_str_str_map.go rename to g/container/gmap/gmap_hash_str_str_map.go diff --git a/g/container/gmap/gmap_link_map.go b/g/container/gmap/gmap_link_map.go new file mode 100644 index 000000000..206037f97 --- /dev/null +++ b/g/container/gmap/gmap_link_map.go @@ -0,0 +1,366 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with gm file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap + +import ( + "github.com/gogf/gf/g/container/glist" + "github.com/gogf/gf/g/container/gvar" + "github.com/gogf/gf/g/internal/rwmutex" +) + +type LinkMap struct { + mu *rwmutex.RWMutex + data map[interface{}]*glist.Element + list *glist.List +} + +type gLinkMapNode struct { + key interface{} + value interface{} +} + +// NewLinkMap returns an empty link map. +// LinkMap is backed by a hash table to store values and doubly-linked list to store ordering. +// The param used to specify whether using map in un-concurrent-safety, +// which is false in default, means concurrent-safe. +func NewLinkMap(unsafe ...bool) *LinkMap { + return &LinkMap{ + mu : rwmutex.New(unsafe...), + data : make(map[interface{}]*glist.Element), + list : glist.New(true), + } +} + +// NewLinkMapFrom returns a link map from given map . +// Note that, the param map will be set as the underlying data map(no deep copy), +// there might be some concurrent-safe issues when changing the map outside. +func NewLinkMapFrom(data map[interface{}]interface{}, unsafe...bool) *LinkMap { + m := NewLinkMap(unsafe...) + m.Sets(data) + return m +} + +// Iterator is alias of IteratorAsc. +func (m *LinkMap) Iterator(f func (key, value interface{}) bool) { + m.IteratorAsc(f) +} + +// IteratorAsc iterates the map in ascending order with given callback function . +// If returns true, then it continues iterating; or false to stop. +func (m *LinkMap) IteratorAsc(f func (key interface{}, value interface{}) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + node := (*gLinkMapNode)(nil) + m.list.IteratorAsc(func(e *glist.Element) bool { + node = e.Value.(*gLinkMapNode) + return f(node.key, node.value) + }) +} + +// IteratorDesc iterates the map in descending order with given callback function . +// If returns true, then it continues iterating; or false to stop. +func (m *LinkMap) IteratorDesc(f func (key interface{}, value interface{}) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + node := (*gLinkMapNode)(nil) + m.list.IteratorDesc(func(e *glist.Element) bool { + node = e.Value.(*gLinkMapNode) + return f(node.key, node.value) + }) +} + +// Clone returns a new link map with copy of current map data. +func (m *LinkMap) Clone(unsafe ...bool) *LinkMap { + return NewLinkMapFrom(m.Map(), unsafe ...) +} + +// Clear deletes all data of the map, it will remake a new underlying data map. +func (m *LinkMap) Clear() { + m.mu.Lock() + m.data = make(map[interface{}]*glist.Element) + m.list = glist.New(true) + m.mu.Unlock() +} + +// Map returns a copy of the data of the map. +func (m *LinkMap) Map() map[interface{}]interface{} { + m.mu.RLock() + node := (*gLinkMapNode)(nil) + data := make(map[interface{}]interface{}, len(m.data)) + m.list.IteratorAsc(func(e *glist.Element) bool { + node = e.Value.(*gLinkMapNode) + data[node.key] = node.value + return true + }) + m.mu.RUnlock() + return data +} + +// Set sets key-value to the map. +func (m *LinkMap) Set(key interface{}, value interface{}) { + m.mu.Lock() + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gLinkMapNode{key, value}) + } else { + e.Value = &gLinkMapNode{key, value} + } + m.mu.Unlock() +} + +// Sets batch sets key-values to the map. +func (m *LinkMap) Sets(data map[interface{}]interface{}) { + m.mu.Lock() + for key, value := range data { + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gLinkMapNode{key, value}) + } else { + e.Value = &gLinkMapNode{key, value} + } + } + m.mu.Unlock() +} + +// Search searches the map with given . +// Second return parameter is true if key was found, otherwise false. +func (m *LinkMap) Search(key interface{}) (value interface{}, found bool) { + m.mu.RLock() + if e, ok := m.data[key]; ok { + value = e.Value.(*gLinkMapNode).value + found = ok + } + m.mu.RUnlock() + return +} + +// Get returns the value by given . +func (m *LinkMap) Get(key interface{}) (value interface{}) { + m.mu.RLock() + if e, ok := m.data[key]; ok { + value = e.Value.(*gLinkMapNode).value + } + m.mu.RUnlock() + return +} + +// doSetWithLockCheck checks whether value of the key exists with mutex.Lock, +// if not exists, set value to the map with given , +// or else just return the existing value. +// +// When setting value, if is type of , +// it will be executed with mutex.Lock of the map, +// and its return value will be set to the map with . +// +// It returns value with given . +func (m *LinkMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} { + m.mu.Lock() + defer m.mu.Unlock() + if e, ok := m.data[key]; ok { + return e.Value.(*gLinkMapNode).value + } + if f, ok := value.(func() interface {}); ok { + value = f() + } + m.data[key] = m.list.PushBack(&gLinkMapNode{key, value}) + return value +} + +// GetOrSet returns the value by key, +// or set value with given if not exist and returns this value. +func (m *LinkMap) GetOrSet(key interface{}, value interface{}) interface{} { + if v, ok := m.Search(key); !ok { + return m.doSetWithLockCheck(key, value) + } else { + return v + } +} + +// GetOrSetFunc returns the value by key, +// or sets value with return value of callback function if not exist +// and returns this value. +func (m *LinkMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} { + if v, ok := m.Search(key); !ok { + return m.doSetWithLockCheck(key, f()) + } else { + return v + } +} + +// GetOrSetFuncLock returns the value by key, +// or sets value with return value of callback function if not exist +// and returns this value. +// +// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function +// with mutex.Lock of the map. +func (m *LinkMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} { + if v, ok := m.Search(key); !ok { + return m.doSetWithLockCheck(key, f) + } else { + return v + } +} + +// GetVar returns a gvar.Var with the value by given . +// The returned gvar.Var is un-concurrent safe. +func (m *LinkMap) GetVar(key interface{}) *gvar.Var { + return gvar.New(m.Get(key), true) +} + +// GetVarOrSet returns a gvar.Var with result from GetVarOrSet. +// The returned gvar.Var is un-concurrent safe. +func (m *LinkMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var { + return gvar.New(m.GetOrSet(key, value), true) +} + +// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc. +// The returned gvar.Var is un-concurrent safe. +func (m *LinkMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var { + return gvar.New(m.GetOrSetFunc(key, f), true) +} + +// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock. +// The returned gvar.Var is un-concurrent safe. +func (m *LinkMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var { + return gvar.New(m.GetOrSetFuncLock(key, f), true) +} + +// SetIfNotExist sets to the map if the does not exist, then return true. +// It returns false if exists, and would be ignored. +func (m *LinkMap) SetIfNotExist(key interface{}, value interface{}) bool { + if !m.Contains(key) { + m.doSetWithLockCheck(key, value) + return true + } + return false +} + +// SetIfNotExistFunc sets value with return value of callback function , then return true. +// It returns false if exists, and would be ignored. +func (m *LinkMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool { + if !m.Contains(key) { + m.doSetWithLockCheck(key, f()) + return true + } + return false +} + +// SetIfNotExistFuncLock sets value with return value of callback function , then return true. +// It returns false if exists, and would be ignored. +// +// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that +// it executes function with mutex.Lock of the map. +func (m *LinkMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool { + if !m.Contains(key) { + m.doSetWithLockCheck(key, f) + return true + } + return false +} + +// Remove deletes value from map by given , and return this deleted value. +func (m *LinkMap) Remove(key interface{}) (value interface{}) { + m.mu.Lock() + if e, ok := m.data[key]; ok { + value = e.Value.(*gLinkMapNode).value + delete(m.data, key) + m.list.Remove(e) + } + m.mu.Unlock() + return +} + +// Removes batch deletes values of the map by keys. +func (m *LinkMap) Removes(keys []interface{}) { + m.mu.Lock() + for _, key := range keys { + if e, ok := m.data[key]; ok { + delete(m.data, key) + m.list.Remove(e) + } + } + m.mu.Unlock() +} + +// Keys returns all keys of the map as a slice in ascending order. +func (m *LinkMap) Keys() []interface{} { + m.mu.RLock() + keys := make([]interface{}, m.list.Len()) + index := 0 + m.list.IteratorAsc(func(e *glist.Element) bool { + keys[index] = e.Value.(*gLinkMapNode).key + index++ + return true + }) + m.mu.RUnlock() + return keys +} + +// Values returns all values of the map as a slice. +func (m *LinkMap) Values() []interface{} { + m.mu.RLock() + values := make([]interface{}, m.list.Len()) + index := 0 + m.list.IteratorAsc(func(e *glist.Element) bool { + values[index] = e.Value.(*gLinkMapNode).value + index++ + return true + }) + m.mu.RUnlock() + return values +} + +// Contains checks whether a key exists. +// It returns true if the exists, or else false. +func (m *LinkMap) Contains(key interface{}) (ok bool) { + m.mu.RLock() + _, ok = m.data[key] + m.mu.RUnlock() + return +} + +// Size returns the size of the map. +func (m *LinkMap) Size() (size int) { + m.mu.RLock() + size = len(m.data) + m.mu.RUnlock() + return +} + +// IsEmpty checks whether the map is empty. +// It returns true if map is empty, or else false. +func (m *LinkMap) IsEmpty() bool { + return m.Size() == 0 +} + +// Flip exchanges key-value of the map to value-key. +func (m *LinkMap) Flip() { + data := m.Map() + m.Clear() + for key, value := range data { + m.Set(value, key) + } +} + +// Merge merges two link maps. +// The map will be merged into the map . +func (m *LinkMap) Merge(other *LinkMap) { + m.mu.Lock() + defer m.mu.Unlock() + if other != m { + other.mu.RLock() + defer other.mu.RUnlock() + } + node := (*gLinkMapNode)(nil) + other.list.IteratorAsc(func(e *glist.Element) bool { + node = e.Value.(*gLinkMapNode) + if e, ok := m.data[node.key]; !ok { + m.data[node.key] = m.list.PushBack(&gLinkMapNode{node.key, node.value}) + } else { + e.Value = &gLinkMapNode{node.key, node.value} + } + return true + }) +} \ No newline at end of file diff --git a/g/container/gmap/gmap_z_link_map_test.go b/g/container/gmap/gmap_z_link_map_test.go new file mode 100644 index 000000000..0fc22e7aa --- /dev/null +++ b/g/container/gmap/gmap_z_link_map_test.go @@ -0,0 +1,120 @@ +// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with gm file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap_test + +import ( + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/container/gmap" + "github.com/gogf/gf/g/test/gtest" + "testing" +) + +func Test_Link_Map_Basic(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewLinkMap() + m.Set("key1", "val1") + gtest.Assert(m.Keys(), []interface{}{"key1"}) + + gtest.Assert(m.Get("key1"), "val1") + gtest.Assert(m.Size(), 1) + gtest.Assert(m.IsEmpty(), false) + + gtest.Assert(m.GetOrSet("key2", "val2"), "val2") + gtest.Assert(m.SetIfNotExist("key2", "val2"), false) + + gtest.Assert(m.SetIfNotExist("key3", "val3"), true) + gtest.Assert(m.Remove("key2"), "val2") + gtest.Assert(m.Contains("key2"), false) + + gtest.AssertIN("key3", m.Keys()) + gtest.AssertIN("key1", m.Keys()) + gtest.AssertIN("val3", m.Values()) + gtest.AssertIN("val1", m.Values()) + + m.Flip() + + gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"}) + + m.Clear() + gtest.Assert(m.Size(), 0) + gtest.Assert(m.IsEmpty(), true) + + m2 := gmap.NewLinkMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"}) + gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"}) + }) +} +func Test_Link_Map_Set_Fun(t *testing.T) { + m := gmap.NewLinkMap() + m.GetOrSetFunc("fun", getValue) + m.GetOrSetFuncLock("funlock", getValue) + gtest.Assert(m.Get("funlock"), 3) + gtest.Assert(m.Get("fun"), 3) + m.GetOrSetFunc("fun", getValue) + gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false) + gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false) +} + +func Test_Link_Map_Batch(t *testing.T) { + m := gmap.NewLinkMap() + m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}) + gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}) + m.Removes([]interface{}{"key1", 1}) + gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"}) +} +func Test_Link_Map_Iterator(t *testing.T){ + expect :=map[interface{}]interface{}{1: 1, "key1": "val1"} + + m := gmap.NewLinkMapFrom(expect) + m.Iterator(func(k interface{}, v interface{}) bool { + gtest.Assert(expect[k], v) + return true + }) + // 断言返回值对遍历控制 + i := 0 + j := 0 + m.Iterator(func(k interface{}, v interface{}) bool { + i++ + return true + }) + m.Iterator(func(k interface{}, v interface{}) bool { + j++ + return false + }) + gtest.Assert(i, 2) + gtest.Assert(j, 1) +} + +func Test_Link_Map_Clone(t *testing.T) { + //clone 方法是深克隆 + m := gmap.NewLinkMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"}) + m_clone := m.Clone() + m.Remove(1) + //修改原 map,clone 后的 map 不影响 + gtest.AssertIN(1, m_clone.Keys()) + + m_clone.Remove("key1") + //修改clone map,原 map 不影响 + gtest.AssertIN("key1", m.Keys()) +} + +func Test_Link_Map_Basic_Merge(t *testing.T) { + m1 := gmap.NewLinkMap() + m2 := gmap.NewLinkMap() + m1.Set("key1", "val1") + m2.Set("key2", "val2") + m1.Merge(m2) + gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"}) +} + +func Test_Link_Map_Order(t *testing.T) { + m := gmap.NewLinkMap() + m.Set("k1", "v1") + m.Set("k2", "v2") + m.Set("k3", "v3") + gtest.Assert(m.Keys(), g.Slice{"k1", "k2", "k3"}) + gtest.Assert(m.Values(), g.Slice{"v1", "v2", "v3"}) +}