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  }