Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions container/gmap/gmap_hash_k_v_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)

// NilChecker is a function that checks whether the given value is nil.
type NilChecker[V any] func(V) bool

// KVMap wraps map type `map[K]V` and provides more map features.
type KVMap[K comparable, V any] struct {
mu rwmutex.RWMutex
data map[K]V
mu rwmutex.RWMutex
data map[K]V
nilChecker NilChecker[V]
}

// NewKVMap creates and returns an empty hash map.
Expand All @@ -41,6 +45,26 @@ func NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V]
return m
}

// RegisterNilChecker registers a custom nil checker function for the map values.
// This function is used to determine if a value should be considered as nil.
// The nil checker function takes a value of type V and returns a boolean indicating
// whether the value should be treated as nil.
func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
m.mu.Lock()
defer m.mu.Unlock()
m.nilChecker = nilChecker
}

// isNil checks whether the given value is nil.
// It first checks if a custom nil checker function is registered and uses it if available,
// otherwise it performs a standard nil check using any(v) == nil.
func (m *KVMap[K, V]) isNil(v V) bool {
if m.nilChecker != nil {
return m.nilChecker(v)
}
return any(v) == nil
}

// Iterator iterates the hash map readonly with custom callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (m *KVMap[K, V]) Iterator(f func(k K, v V) bool) {
Expand Down Expand Up @@ -217,8 +241,7 @@ func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) {
if v, ok := m.data[key]; ok {
return v, true
}

if any(value) != nil {
if !m.isNil(value) {
m.data[key] = value
}
return value, false
Expand Down Expand Up @@ -255,7 +278,7 @@ func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
return v
}
value := f()
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = value
}
return value
Expand Down
37 changes: 29 additions & 8 deletions container/gmap/gmap_list_k_v_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import (
//
// Reference: http://en.wikipedia.org/wiki/Associative_array
type ListKVMap[K comparable, V any] struct {
mu rwmutex.RWMutex
data map[K]*glist.TElement[*gListKVMapNode[K, V]]
list *glist.TList[*gListKVMapNode[K, V]]
mu rwmutex.RWMutex
data map[K]*glist.TElement[*gListKVMapNode[K, V]]
list *glist.TList[*gListKVMapNode[K, V]]
nilChecker NilChecker[V]
}

type gListKVMapNode[K comparable, V any] struct {
Expand Down Expand Up @@ -58,6 +59,26 @@ func NewListKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *ListKVMa
return m
}

// RegisterNilChecker registers a custom nil checker function for the map values.
// This function is used to determine if a value should be considered as nil.
// The nil checker function takes a value of type V and returns a boolean indicating
// whether the value should be treated as nil.
func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
m.mu.Lock()
defer m.mu.Unlock()
m.nilChecker = nilChecker
}

// isNil checks whether the given value is nil.
// It first checks if a custom nil checker function is registered and uses it if available,
// otherwise it performs a standard nil check using any(v) == nil.
func (m *ListKVMap[K, V]) isNil(v V) bool {
if m.nilChecker != nil {
return m.nilChecker(v)
}
return any(v) == nil
}

// Iterator is alias of IteratorAsc.
func (m *ListKVMap[K, V]) Iterator(f func(key K, value V) bool) {
m.IteratorAsc(f)
Expand Down Expand Up @@ -282,7 +303,7 @@ func (m *ListKVMap[K, V]) doSetWithLockCheckWithoutLock(key K, value V) V {
if e, ok := m.data[key]; ok {
return e.Value.value
}
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
}
return value
Expand Down Expand Up @@ -327,7 +348,7 @@ func (m *ListKVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
return e.Value.value
}
value := f()
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
}
return value
Expand Down Expand Up @@ -370,7 +391,7 @@ func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
if _, ok := m.data[key]; ok {
return false
}
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
}
return true
Expand All @@ -390,7 +411,7 @@ func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
return false
}
value := f()
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
}
return true
Expand All @@ -413,7 +434,7 @@ func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
return false
}
value := f()
if any(value) != nil {
if !m.isNil(value) {
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
}
return true
Expand Down
33 changes: 33 additions & 0 deletions container/gmap/gmap_z_unit_k_v_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1630,3 +1630,36 @@ func Test_KVMap_Flip_String(t *testing.T) {
t.Assert(m.Get("val2"), "key2")
})
}

// Test TypedNil with custom nil checker for pointers
func Test_KVMap_TypedNil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Student struct {
Name string
Age int
}
m1 := gmap.NewKVMap[int, *Student](true)
for i := 0; i < 10; i++ {
m1.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
t.Assert(m1.Size(), 10)
m2 := gmap.NewKVMap[int, *Student](true)
m2.RegisterNilChecker(func(student *Student) bool {
return student == nil
})
for i := 0; i < 10; i++ {
m2.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
t.Assert(m2.Size(), 5)
})
}
33 changes: 33 additions & 0 deletions container/gmap/gmap_z_unit_list_k_v_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1341,3 +1341,36 @@ func Test_ListKVMap_UnmarshalValue_NilData(t *testing.T) {
t.Assert(m.Get("b"), "2")
})
}

// Test typed nil values
func Test_ListKVMap_TypedNil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Student struct {
Name string
Age int
}
m1 := gmap.NewListKVMap[int, *Student](true)
for i := 0; i < 10; i++ {
m1.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
t.Assert(m1.Size(), 10)
m2 := gmap.NewListKVMap[int, *Student](true)
m2.RegisterNilChecker(func(student *Student) bool {
return student == nil
})
for i := 0; i < 10; i++ {
m2.GetOrSetFuncLock(i, func() *Student {
if i%2 == 0 {
return &Student{}
}
return nil
})
}
t.Assert(m2.Size(), 5)
})
}
34 changes: 29 additions & 5 deletions container/gset/gset_t_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)

// NilChecker is a function that checks whether the given value is nil.
type NilChecker[T any] func(T) bool

// TSet[T] is consisted of any items.
type TSet[T comparable] struct {
mu rwmutex.RWMutex
data map[T]struct{}
mu rwmutex.RWMutex
data map[T]struct{}
nilChecker NilChecker[T]
}

// NewTSet creates and returns a new set, which contains un-repeated items.
Expand All @@ -43,6 +47,26 @@ func NewTSetFrom[T comparable](items []T, safe ...bool) *TSet[T] {
}
}

// RegisterNilChecker registers a custom nil checker function for the set elements.
// This function is used to determine if an element should be considered as nil.
// The nil checker function takes an element of type T and returns a boolean indicating
// whether the element should be treated as nil.
func (set *TSet[T]) RegisterNilChecker(nilChecker NilChecker[T]) {
set.mu.Lock()
defer set.mu.Unlock()
set.nilChecker = nilChecker
}

// isNil checks whether the given value is nil.
// It first checks if a custom nil checker function is registered and uses it if available,
// otherwise it performs a standard nil check using any(v) == nil.
func (set *TSet[T]) isNil(v T) bool {
if set.nilChecker != nil {
return set.nilChecker(v)
}
return any(v) == nil
}

// Iterator iterates the set readonly with given callback function `f`,
// if `f` returns true then continue iterating; or false to stop.
func (set *TSet[T]) Iterator(f func(v T) bool) {
Expand Down Expand Up @@ -71,7 +95,7 @@ func (set *TSet[T]) Add(items ...T) {
//
// Note that, if `item` is nil, it does nothing and returns false.
func (set *TSet[T]) AddIfNotExist(item T) bool {
if any(item) == nil {
if set.isNil(item) {
return false
}
if !set.Contains(item) {
Expand All @@ -95,7 +119,7 @@ func (set *TSet[T]) AddIfNotExist(item T) bool {
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
// is executed without writing lock.
func (set *TSet[T]) AddIfNotExistFunc(item T, f func() bool) bool {
if any(item) == nil {
if set.isNil(item) {
return false
}
if !set.Contains(item) {
Expand All @@ -121,7 +145,7 @@ func (set *TSet[T]) AddIfNotExistFunc(item T, f func() bool) bool {
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
// is executed within writing lock.
func (set *TSet[T]) AddIfNotExistFuncLock(item T, f func() bool) bool {
if any(item) == nil {
if set.isNil(item) {
return false
}
if !set.Contains(item) {
Expand Down
20 changes: 20 additions & 0 deletions container/gset/gset_z_unit_t_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,23 @@ func TestTSet_RLockFunc(t *testing.T) {
t.Assert(sum, 6)
})
}

func Test_TSet_TypedNil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Student struct {
Name string
Age int
}
set := gset.NewTSet[*Student](true)
var s *Student = nil
exist := set.AddIfNotExist(s)
t.Assert(exist, true)

set2 := gset.NewTSet[*Student](true)
set2.RegisterNilChecker(func(student *Student) bool {
return student == nil
})
exist2 := set2.AddIfNotExist(s)
t.Assert(exist2, false)
})
}
26 changes: 25 additions & 1 deletion container/gtree/gtree_k_v_avltree.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)

// NilChecker is a function that checks whether the given value is nil.
type NilChecker[V any] func(V) bool

// AVLKVTree holds elements of the AVL tree.
type AVLKVTree[K comparable, V any] struct {
mu rwmutex.RWMutex
comparator func(v1, v2 K) int
tree *avltree.Tree[K, V]
nilChecker NilChecker[V]
}

// AVLKVTreeNode is a single element within the tree.
Expand Down Expand Up @@ -54,6 +58,26 @@ func NewAVLKVTreeFrom[K comparable, V any](comparator func(v1, v2 K) int, data m
return tree
}

// RegisterNilChecker registers a custom nil checker function for the map values.
// This function is used to determine if a value should be considered as nil.
// The nil checker function takes a value of type V and returns a boolean indicating
// whether the value should be treated as nil.
func (tree *AVLKVTree[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.nilChecker = nilChecker
}

// isNil checks whether the given value is nil.
// It first checks if a custom nil checker function is registered and uses it if available,
// otherwise it performs a standard nil check using any(v) == nil.
func (tree *AVLKVTree[K, V]) isNil(value V) bool {
if tree.nilChecker != nil {
return tree.nilChecker(value)
}
return any(value) == nil
}

// Clone clones and returns a new tree from current tree.
func (tree *AVLKVTree[K, V]) Clone() *AVLKVTree[K, V] {
if tree == nil {
Expand Down Expand Up @@ -518,7 +542,7 @@ func (tree *AVLKVTree[K, V]) Flip(comparator ...func(v1, v2 K) int) {
//
// It returns value with given `key`.
func (tree *AVLKVTree[K, V]) doSet(key K, value V) V {
if any(value) == nil {
if tree.isNil(value) {
return value
}
tree.tree.Put(key, value)
Expand Down
Loading
Loading