From 13524a36bcb7f6883cc3f33e001bdc2b37c749e7 Mon Sep 17 00:00:00 2001 From: Lance Add <1196661499@qq.com> Date: Thu, 15 Jan 2026 10:18:05 +0800 Subject: [PATCH] fix(container): Add NilChecker Support to gmap, gset, and gtree for Typed Nil Issue Resolution (#4605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 描述 本PR为`gmap`、`gset`和`gtree`容器引入了`NilChecker`机制,以解决Go语言中的`typed nil`问题。该实现允许用户注册自定义的nil检查函数来确定值是否应被视为nil,这对于处理那些会被存储到容器中的`typed nil`值特别有用。 ## 情况描述 当前`gmap`等容器的泛型容器存在对`value`的`nil`值无法正确过滤的问题,例如以下例子中如果使用默认的`if any(value) != nil`去判断就会得到错误的结果,原因是会出现带有类型的`(*Student)(nil)`直接和`nil`比较或者使用`any`强转都是不对的,使用反射可以解决但是性能太差了,所以换个思虑我们让用户自己决定如何判断`nil`就能解决这个问题 ```golang func main() { 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 }) } fmt.Println(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 }) } fmt.Println(m2.Size()) // 5 } ``` ## 变更内容 - 在gmap、gset和gtree包中添加了`NilChecker`类型定义 - 扩展容器结构体,增加`nilChecker`字段来存储自定义nil检查函数 - 实现了`RegisterNilChecker`方法,允许用户注册自定义nil检查逻辑 - 添加了`isNil`内部方法,优先使用自定义nil检查函数或回退到默认的`any(v) == nil`检查 - 更新关键操作(AddIfNotExist、Set等)以利用nil检查机制 - 为所有三个容器类型添加了全面的测试用例以验证nilchecker功能 --------- Co-authored-by: github-actions[bot] Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_hash_k_v_map.go | 33 ++++- container/gmap/gmap_list_k_v_map.go | 37 ++++-- container/gmap/gmap_z_unit_k_v_map_test.go | 33 +++++ .../gmap/gmap_z_unit_list_k_v_map_test.go | 33 +++++ container/gset/gset_t_set.go | 34 +++++- container/gset/gset_z_unit_t_set_test.go | 20 +++ container/gtree/gtree_k_v_avltree.go | 26 +++- container/gtree/gtree_k_v_btree.go | 23 +++- container/gtree/gtree_k_v_redblacktree.go | 23 +++- container/gtree/gtree_redblacktree.go | 2 +- container/gtree/gtree_z_k_v_tree_test.go | 114 ++++++++++++++++++ 11 files changed, 356 insertions(+), 22 deletions(-) create mode 100644 container/gtree/gtree_z_k_v_tree_test.go diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index 0b9f9c8ea..704e66298 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -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. @@ -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) { @@ -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 @@ -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 diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 6bfe2a7e9..c23bf262b 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -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 { @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/container/gmap/gmap_z_unit_k_v_map_test.go b/container/gmap/gmap_z_unit_k_v_map_test.go index faef2c316..a904c8b79 100644 --- a/container/gmap/gmap_z_unit_k_v_map_test.go +++ b/container/gmap/gmap_z_unit_k_v_map_test.go @@ -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) + }) +} diff --git a/container/gmap/gmap_z_unit_list_k_v_map_test.go b/container/gmap/gmap_z_unit_list_k_v_map_test.go index 7714f532f..4fa02d5e5 100644 --- a/container/gmap/gmap_z_unit_list_k_v_map_test.go +++ b/container/gmap/gmap_z_unit_list_k_v_map_test.go @@ -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) + }) +} diff --git a/container/gset/gset_t_set.go b/container/gset/gset_t_set.go index ae3507cec..4367ceee1 100644 --- a/container/gset/gset_t_set.go +++ b/container/gset/gset_t_set.go @@ -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. @@ -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) { @@ -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) { @@ -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) { @@ -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) { diff --git a/container/gset/gset_z_unit_t_set_test.go b/container/gset/gset_z_unit_t_set_test.go index 4db85fb13..f97a85e6b 100644 --- a/container/gset/gset_z_unit_t_set_test.go +++ b/container/gset/gset_z_unit_t_set_test.go @@ -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) + }) +} diff --git a/container/gtree/gtree_k_v_avltree.go b/container/gtree/gtree_k_v_avltree.go index 323097039..afa7f8e9f 100644 --- a/container/gtree/gtree_k_v_avltree.go +++ b/container/gtree/gtree_k_v_avltree.go @@ -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. @@ -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 { @@ -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) diff --git a/container/gtree/gtree_k_v_btree.go b/container/gtree/gtree_k_v_btree.go index e1a4399ec..f76f8d806 100644 --- a/container/gtree/gtree_k_v_btree.go +++ b/container/gtree/gtree_k_v_btree.go @@ -24,6 +24,7 @@ type BKVTree[K comparable, V any] struct { comparator func(v1, v2 K) int m int // order (maximum number of children) tree *btree.Tree[K, V] + nilChecker NilChecker[V] } // BKVTreeEntry represents the key-value pair contained within nodes. @@ -56,6 +57,26 @@ func NewBKVTreeFrom[K comparable, V any](m int, comparator func(v1, v2 K) int, d 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 *BKVTree[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 *BKVTree[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 *BKVTree[K, V]) Clone() *BKVTree[K, V] { if tree == nil { @@ -453,7 +474,7 @@ func (tree *BKVTree[K, V]) Right() *BKVTreeEntry[K, V] { // // It returns value with given `key`. func (tree *BKVTree[K, V]) doSet(key K, value V) V { - if any(value) == nil { + if tree.isNil(value) { return value } tree.tree.Put(key, value) diff --git a/container/gtree/gtree_k_v_redblacktree.go b/container/gtree/gtree_k_v_redblacktree.go index 5b0be020b..1b3eae131 100644 --- a/container/gtree/gtree_k_v_redblacktree.go +++ b/container/gtree/gtree_k_v_redblacktree.go @@ -24,6 +24,7 @@ type RedBlackKVTree[K comparable, V any] struct { mu rwmutex.RWMutex comparator func(v1, v2 K) int tree *redblacktree.Tree[K, V] + nilChecker NilChecker[V] } // RedBlackKVTreeNode is a single element within the tree. @@ -75,6 +76,26 @@ func RedBlackKVTreeInitFrom[K comparable, V any](tree *RedBlackKVTree[K, V], com } } +// 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 *RedBlackKVTree[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 *RedBlackKVTree[K, V]) isNil(value V) bool { + if tree.nilChecker != nil { + return tree.nilChecker(value) + } + return any(value) == nil +} + // SetComparator sets/changes the comparator for sorting. func (tree *RedBlackKVTree[K, V]) SetComparator(comparator func(a, b K) int) { tree.comparator = comparator @@ -592,7 +613,7 @@ func (tree *RedBlackKVTree[K, V]) UnmarshalValue(value any) (err error) { // // It returns value with given `key`. func (tree *RedBlackKVTree[K, V]) doSet(key K, value V) (ret V) { - if any(value) == nil { + if tree.isNil(value) { return } tree.tree.Put(key, value) diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index 25138b243..9735075fb 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -46,7 +46,7 @@ func NewRedBlackTreeFrom(comparator func(v1, v2 any) int, data map[any]any, safe func (tree *RedBlackTree) lazyInit() { tree.once.Do(func() { if tree.RedBlackKVTree == nil { - tree.RedBlackKVTree = NewRedBlackKVTree[any, any](gutil.ComparatorTStr, false) + tree.RedBlackKVTree = NewRedBlackKVTree[any, any](gutil.ComparatorTStr[any], false) } }) } diff --git a/container/gtree/gtree_z_k_v_tree_test.go b/container/gtree/gtree_z_k_v_tree_test.go new file mode 100644 index 000000000..e346ab665 --- /dev/null +++ b/container/gtree/gtree_z_k_v_tree_test.go @@ -0,0 +1,114 @@ +// Copyright GoFrame Author(https://goframe.org). 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 this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtree_test + +import ( + "testing" + + "github.com/gogf/gf/v2/container/gtree" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gutil" +) + +func Test_KVAVLTree_TypedNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Student struct { + Name string + Age int + } + avlTree := gtree.NewAVLKVTree[int, *Student](gutil.ComparatorTStr[int], true) + for i := 0; i < 10; i++ { + if i%2 == 0 { + avlTree.Set(i, &Student{}) + } else { + var s *Student = nil + avlTree.Set(i, s) + } + } + t.Assert(avlTree.Size(), 10) + avlTree2 := gtree.NewAVLKVTree[int, *Student](gutil.ComparatorTStr[int], true) + avlTree2.RegisterNilChecker(func(student *Student) bool { + return student == nil + }) + for i := 0; i < 10; i++ { + if i%2 == 0 { + avlTree2.Set(i, &Student{}) + } else { + var s *Student = nil + avlTree2.Set(i, s) + } + } + t.Assert(avlTree2.Size(), 5) + + }) +} + +func Test_KVBTree_TypedNil(t *testing.T) { + type Student struct { + Name string + Age int + } + gtest.C(t, func(t *gtest.T) { + btree := gtree.NewBKVTree[int, *Student](100, gutil.ComparatorTStr[int], true) + for i := 0; i < 10; i++ { + if i%2 == 0 { + btree.Set(i, &Student{}) + } else { + var s *Student = nil + btree.Set(i, s) + } + } + t.Assert(btree.Size(), 10) + btree2 := gtree.NewBKVTree[int, *Student](100, gutil.ComparatorTStr[int], true) + btree2.RegisterNilChecker(func(student *Student) bool { + return student == nil + }) + for i := 0; i < 10; i++ { + if i%2 == 0 { + btree2.Set(i, &Student{}) + } else { + var s *Student = nil + btree2.Set(i, s) + } + } + t.Assert(btree2.Size(), 5) + }) + +} + +func Test_KVRedBlackTree_TypedNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Student struct { + Name string + Age int + } + redBlackTree := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true) + for i := 0; i < 10; i++ { + if i%2 == 0 { + redBlackTree.Set(i, &Student{}) + } else { + var s *Student = nil + redBlackTree.Set(i, s) + } + } + t.Assert(redBlackTree.Size(), 10) + redBlackTree2 := gtree.NewRedBlackKVTree[int, *Student](gutil.ComparatorTStr[int], true) + + redBlackTree2.RegisterNilChecker(func(student *Student) bool { + return student == nil + }) + for i := 0; i < 10; i++ { + if i%2 == 0 { + redBlackTree2.Set(i, &Student{}) + } else { + var s *Student = nil + redBlackTree2.Set(i, s) + } + } + t.Assert(redBlackTree2.Size(), 5) + }) +}