github.com/songzhibin97/go-baseutils@v0.0.2-0.20240302024150-487d8ce9c082/structure/sets/skipset/skipset_test.go (about) 1 package skipset 2 3 import ( 4 "fmt" 5 "math" 6 "sort" 7 "strconv" 8 "sync" 9 "sync/atomic" 10 "testing" 11 12 "github.com/songzhibin97/go-baseutils/base/bcomparator" 13 "github.com/songzhibin97/go-baseutils/sys/fastrand" 14 ) 15 16 func Example() { 17 l := New[int](bcomparator.IntComparator()) 18 19 for _, v := range []int{10, 12, 15} { 20 if l.AddB(v) { 21 fmt.Println("skipset add", v) 22 } 23 } 24 25 if l.Contains(10) { 26 fmt.Println("skipset contains 10") 27 } 28 29 l.Range(func(value int) bool { 30 fmt.Println("skipset range found ", value) 31 return true 32 }) 33 34 l.Remove(15) 35 fmt.Printf("skipset contains %d items\r\n", l.Len()) 36 } 37 38 func TestIntSet(t *testing.T) { 39 // Correctness. 40 l := New[int](bcomparator.IntComparator()) 41 if l.length != 0 { 42 t.Fatal("invalid length") 43 } 44 if l.Contains(0) { 45 t.Fatal("invalid contains") 46 } 47 48 if !l.AddB(0) || l.length != 1 { 49 t.Fatal("invalid add") 50 } 51 if !l.Contains(0) { 52 t.Fatal("invalid contains") 53 } 54 if !l.RemoveB(0) || l.length != 0 { 55 t.Fatal("invalid remove") 56 } 57 58 if !l.AddB(20) || l.length != 1 { 59 t.Fatal("invalid add") 60 } 61 if !l.AddB(22) || l.length != 2 { 62 t.Fatal("invalid add") 63 } 64 if !l.AddB(21) || l.length != 3 { 65 t.Fatal("invalid add") 66 } 67 68 var i int 69 l.Range(func(score int) bool { 70 if i == 0 && score != 20 { 71 t.Fatal("invalid range") 72 } 73 if i == 1 && score != 21 { 74 t.Fatal("invalid range") 75 } 76 if i == 2 && score != 22 { 77 t.Fatal("invalid range") 78 } 79 i++ 80 return true 81 }) 82 83 if !l.RemoveB(21) || l.length != 2 { 84 t.Fatal("invalid remove") 85 } 86 87 i = 0 88 l.Range(func(score int) bool { 89 if i == 0 && score != 20 { 90 t.Fatal("invalid range") 91 } 92 if i == 1 && score != 22 { 93 t.Fatal("invalid range") 94 } 95 i++ 96 return true 97 }) 98 99 const num = math.MaxInt16 100 // Make rand shuffle array. 101 // The testArray contains [1,num] 102 testArray := make([]int, num) 103 testArray[0] = num + 1 104 for i := 1; i < num; i++ { 105 // We left 0, because it is the default score for head and tail. 106 // If we check the skipset contains 0, there must be something wrong. 107 testArray[i] = int(i) 108 } 109 for i := len(testArray) - 1; i > 0; i-- { // Fisher–Yates shuffle 110 j := fastrand.Uint32n(uint32(i + 1)) 111 testArray[i], testArray[j] = testArray[j], testArray[i] 112 } 113 114 // Concurrent add. 115 var wg sync.WaitGroup 116 for i := 0; i < num; i++ { 117 i := i 118 wg.Add(1) 119 go func() { 120 l.Add(testArray[i]) 121 wg.Done() 122 }() 123 } 124 wg.Wait() 125 if l.length != int64(num) { 126 t.Fatalf("invalid length expected %d, got %d", num, l.length) 127 } 128 129 // Don't contains 0 after concurrent addion. 130 if l.Contains(0) { 131 t.Fatal("contains 0 after concurrent addion") 132 } 133 134 // Concurrent contains. 135 for i := 0; i < num; i++ { 136 i := i 137 wg.Add(1) 138 go func() { 139 if !l.Contains(testArray[i]) { 140 wg.Done() 141 panic(fmt.Sprintf("add doesn't contains %d", i)) 142 } 143 wg.Done() 144 }() 145 } 146 wg.Wait() 147 148 // Concurrent remove. 149 for i := 0; i < num; i++ { 150 i := i 151 wg.Add(1) 152 go func() { 153 if !l.RemoveB(testArray[i]) { 154 wg.Done() 155 panic(fmt.Sprintf("can't remove %d", i)) 156 } 157 wg.Done() 158 }() 159 } 160 wg.Wait() 161 if l.length != 0 { 162 t.Fatalf("invalid length expected %d, got %d", 0, l.length) 163 } 164 165 // Test all methods. 166 const smallRndN = 1 << 8 167 for i := 0; i < 1<<16; i++ { 168 wg.Add(1) 169 go func() { 170 r := fastrand.Uint32n(num) 171 if r < 333 { 172 l.Add(int(fastrand.Uint32n(smallRndN)) + 1) 173 } else if r < 666 { 174 l.Contains(int(fastrand.Uint32n(smallRndN)) + 1) 175 } else if r != 999 { 176 l.Remove(int(fastrand.Uint32n(smallRndN)) + 1) 177 } else { 178 var pre int 179 l.Range(func(score int) bool { 180 if score <= pre { // 0 is the default value for header and tail score 181 panic("invalid content") 182 } 183 pre = score 184 return true 185 }) 186 } 187 wg.Done() 188 }() 189 } 190 wg.Wait() 191 192 // Correctness 2. 193 var ( 194 x = New[int](bcomparator.IntComparator()) 195 y = New[int](bcomparator.IntComparator()) 196 count = 10000 197 ) 198 199 for i := 0; i < count; i++ { 200 x.Add(i) 201 } 202 203 for i := 0; i < 16; i++ { 204 wg.Add(1) 205 go func() { 206 x.Range(func(score int) bool { 207 if x.RemoveB(score) { 208 if !y.AddB(score) { 209 panic("invalid add") 210 } 211 } 212 return true 213 }) 214 wg.Done() 215 }() 216 } 217 wg.Wait() 218 if x.Len() != 0 || y.Len() != count { 219 t.Fatal("invalid length") 220 } 221 222 // Concurrent Add and Remove in small zone. 223 x = New[int](bcomparator.IntComparator()) 224 var ( 225 addcount uint64 = 0 226 removecount uint64 = 0 227 ) 228 229 for i := 0; i < 16; i++ { 230 wg.Add(1) 231 go func() { 232 for i := 0; i < 1000; i++ { 233 if fastrand.Uint32n(2) == 0 { 234 if x.RemoveB(int(fastrand.Uint32n(10))) { 235 atomic.AddUint64(&removecount, 1) 236 } 237 } else { 238 if x.AddB(int(fastrand.Uint32n(10))) { 239 atomic.AddUint64(&addcount, 1) 240 } 241 } 242 } 243 wg.Done() 244 }() 245 } 246 wg.Wait() 247 if addcount < removecount { 248 panic("invalid count") 249 } 250 if addcount-removecount != uint64(x.Len()) { 251 panic("invalid count") 252 } 253 254 pre := -1 255 x.Range(func(score int) bool { 256 if score <= pre { 257 panic("invalid content") 258 } 259 pre = score 260 return true 261 }) 262 } 263 264 func TestIntSetDesc(t *testing.T) { 265 s := New[int](bcomparator.ReverseComparator(bcomparator.IntComparator())) 266 nums := []int{-1, 0, 5, 12} 267 for _, v := range nums { 268 s.Add(v) 269 } 270 i := len(nums) - 1 271 s.Range(func(value int) bool { 272 if nums[i] != value { 273 t.Fatal("error") 274 } 275 i-- 276 return true 277 }) 278 } 279 280 func TestStringSet(t *testing.T) { 281 x := New[string](bcomparator.StringComparator()) 282 if !x.AddB("111") || x.Len() != 1 { 283 t.Fatal("invalid") 284 } 285 if !x.AddB("222") || x.Len() != 2 { 286 t.Fatal("invalid") 287 } 288 if x.AddB("111") || x.Len() != 2 { 289 t.Fatal("invalid") 290 } 291 if !x.Contains("111") || !x.Contains("222") { 292 t.Fatal("invalid") 293 } 294 if !x.RemoveB("111") || x.Len() != 1 { 295 t.Fatal("invalid") 296 } 297 if !x.RemoveB("222") || x.Len() != 0 { 298 t.Fatal("invalid") 299 } 300 301 var wg sync.WaitGroup 302 for i := 0; i < 100; i++ { 303 wg.Add(1) 304 i := i 305 go func() { 306 if !x.AddB(strconv.Itoa(i)) { 307 panic("invalid") 308 } 309 wg.Done() 310 }() 311 } 312 wg.Wait() 313 314 tmp := make([]int, 0, 100) 315 x.Range(func(val string) bool { 316 res, _ := strconv.Atoi(val) 317 tmp = append(tmp, res) 318 return true 319 }) 320 sort.Ints(tmp) 321 for i := 0; i < 100; i++ { 322 if i != tmp[i] { 323 t.Fatal("invalid") 324 } 325 } 326 }