package skiplist import ( "fmt" "math" "math/rand" "testing" "time" //"github.com/pkg/profile" ) const ( maxN = 1000000 ) type Element int func (e Element) ExtractKey() int64 { return int64(e) } func (e Element) String() string { return fmt.Sprintf("%03d", e) } type FloatElement float64 func (e FloatElement) ExtractKey() int64 { return int64(e) } func (e FloatElement) String() string { return fmt.Sprintf("%.3f", e) } type ComplexElement struct { E int S string } func (e ComplexElement) ExtractKey() int64 { return int64(e.E) } func (e ComplexElement) String() string { return fmt.Sprintf("%03d", e.E) } // timeTrack will print out the number of nanoseconds since the start time divided by n // Useful for printing out how long each iteration took in a benchmark func timeTrack(start time.Time, n int, name string) { loopNS := time.Since(start).Nanoseconds() / int64(n) fmt.Printf("%s: %d\n", name, loopNS) } func TestInsertAndFind(t *testing.T) { t.Run("nil list", func(t *testing.T) { var listPointer *SkipList listPointer.Insert(Element(0)) if _, ok := listPointer.Find(Element(0)); ok { t.Error("should not find element in nil list") } }) t.Run("empty list", func(t *testing.T) { list := New() if _, ok := list.Find(Element(0)); ok { t.Error("should not find element in empty list") } if !list.IsEmpty() { t.Error("list should be empty") } }) t.Run("sequential insert at beginning", func(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(maxN - i)) } for i := 0; i < maxN; i++ { if _, ok := list.Find(Element(maxN - i)); !ok { t.Errorf("failed to find element %d", maxN-i) } } }) t.Run("sequential insert at end", func(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(i)) } for i := 0; i < maxN; i++ { if _, ok := list.Find(Element(i)); !ok { t.Errorf("failed to find element %d", i) } } }) t.Run("random insert", func(t *testing.T) { list := New() rList := rand.Perm(maxN) for _, e := range rList { list.Insert(Element(e)) } for _, e := range rList { if _, ok := list.Find(Element(e)); !ok { t.Errorf("failed to find element %d", e) } } }) t.Run("boundary values", func(t *testing.T) { list := New() // Test with min/max int values list.Insert(Element(math.MinInt64)) list.Insert(Element(math.MaxInt64)) if _, ok := list.Find(Element(math.MinInt64)); !ok { t.Error("failed to find MinInt64") } if _, ok := list.Find(Element(math.MaxInt64)); !ok { t.Error("failed to find MaxInt64") } }) t.Run("duplicate values", func(t *testing.T) { list := New() // Insert same value multiple times for i := 0; i < 10; i++ { list.Insert(Element(42)) } // Should be able to find the value if _, ok := list.Find(Element(42)); !ok { t.Error("failed to find duplicate value") } // Count should reflect all insertions count := 0 node, _ := list.Find(Element(42)) for node != nil && node.GetKey() == 42 { count++ node = list.Next(node) } if count != 10 { t.Errorf("expected 10 duplicate values, got %d", count) } }) } func TestDelete(t *testing.T) { var list SkipList // Delete on empty list list.Delete(Element(0)) list = New() list.Delete(Element(0)) if !list.IsEmpty() { t.Fail() } // Delete elements at the beginning of the list. for i := 0; i < maxN; i++ { list.Insert(Element(i)) } for i := 0; i < maxN; i++ { list.Delete(Element(i)) } if !list.IsEmpty() { t.Fail() } list = New() // Delete elements at the end of the list. for i := 0; i < maxN; i++ { list.Insert(Element(i)) } for i := 0; i < maxN; i++ { list.Delete(Element(maxN - i - 1)) } if !list.IsEmpty() { t.Fail() } list = New() // Delete elements at random positions in the list. rList := rand.Perm(maxN) for _, e := range rList { list.Insert(Element(e)) } for _, e := range rList { list.Delete(Element(e)) } if !list.IsEmpty() { t.Fail() } } func TestFindGreaterOrEqual(t *testing.T) { eps := 0.00000001 maxNumber := 1000.0 var list SkipList var listPointer *SkipList // Test on empty list. if _, ok := listPointer.FindGreaterOrEqual(FloatElement(0)); ok { t.Fail() } list = NewEps(eps) for i := 0; i < maxN; i++ { list.Insert(FloatElement(rand.Float64() * maxNumber)) } first := float64(list.GetSmallestNode().GetValue().(FloatElement)) // Find the very first element. This is a special case in the implementation that needs testing! if v, ok := list.FindGreaterOrEqual(FloatElement(first - 2.0*eps)); ok { // We found an element different to the first one! if math.Abs(float64(v.GetValue().(FloatElement))-first) > eps { t.Fail() } } else { // No element found. t.Fail() } for i := 0; i < maxN; i++ { f := rand.Float64() * maxNumber if v, ok := list.FindGreaterOrEqual(FloatElement(f)); ok { // if f is v should be bigger than the element before lastV := float64(list.Prev(v).GetValue().(FloatElement)) thisV := float64(v.GetValue().(FloatElement)) isFirst := math.Abs(first-thisV) <= eps if !isFirst && lastV >= f { fmt.Printf("PrevV: %.8f\n f: %.8f\n\n", lastV, f) t.Fail() } // v should be bigger or equal to f // If we compare directly, we get an equal key with a difference on the 10th decimal point, which fails. if f-thisV > eps { fmt.Printf("f: %.8f\nv: %.8f\n\n", f, thisV) t.Fail() } } else { lastV := float64(list.GetLargestNode().GetValue().(FloatElement)) // It is OK, to fail, as long as f is bigger than the last element. if f <= lastV { fmt.Printf("lastV: %.8f\n f: %.8f\n\n", lastV, f) t.Fail() } } } } func TestPrev(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(i)) } smallest := list.GetSmallestNode() largest := list.GetLargestNode() lastNode := largest node := lastNode for node != smallest { node = list.Prev(node) // Must always be incrementing here! if node.value.(Element) >= lastNode.value.(Element) { t.Fail() } // Next.Prev must always point to itself! if list.Prev(list.Next(node)) != node { t.Fail() } lastNode = node } if list.Prev(smallest) != largest { t.Fail() } } func TestNext(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(i)) } smallest := list.GetSmallestNode() largest := list.GetLargestNode() lastNode := smallest node := lastNode for node != largest { node = list.Next(node) // Must always be incrementing here! if node.value.(Element) <= lastNode.value.(Element) { t.Fail() } // Next.Prev must always point to itself! if list.Next(list.Prev(node)) != node { t.Fail() } lastNode = node } if list.Next(largest) != smallest { t.Fail() } } func TestChangeValue(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(ComplexElement{i, "value"}) } for i := 0; i < maxN; i++ { // The key only looks at the int so the string doesn't matter here! f1, ok := list.Find(ComplexElement{i, ""}) if !ok { t.Fail() } ok = list.ChangeValue(f1, ComplexElement{i, "different value"}) if !ok { t.Fail() } f2, ok := list.Find(ComplexElement{i, ""}) if !ok { t.Fail() } if f2.GetValue().(ComplexElement).S != "different value" { t.Fail() } if ok = list.ChangeValue(f2, ComplexElement{i + 5, "different key"}); ok { t.Fail() } } } func TestGetNodeCount(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(i)) } if list.GetNodeCount() != maxN { t.Fail() } } func TestString(t *testing.T) { list := NewSeed(1531889620180049576) for i := 0; i < 20; i++ { list.Insert(Element(i)) } testString := ` --> [000] -> [002] -> [009] -> [010] 000: [---|001] 001: [000|002] 002: [001|003] -> [004] 003: [002|004] 004: [003|005] -> [005] 005: [004|006] -> [009] 006: [005|007] 007: [006|008] 008: [007|009] 009: [008|010] -> [010] -> [010] 010: [009|011] -> [012] -> [---] -> [---] 011: [010|012] 012: [011|013] -> [013] 013: [012|014] -> [---] 014: [013|015] 015: [014|016] 016: [015|017] 017: [016|018] 018: [017|019] 019: [018|---] --> [019] -> [013] -> [010] -> [010] ` if list.String() != testString { t.Fail() } } func TestInfiniteLoop(t *testing.T) { list := New() list.Insert(Element(1)) if _, ok := list.Find(Element(2)); ok { t.Fail() } if _, ok := list.FindGreaterOrEqual(Element(2)); ok { t.Fail() } } for i := 0; i < maxN; i++ { // The key only looks at the int so the string doesn't matter here! f1, ok := list.Find(ComplexElement{i, ""}) if !ok { t.Fail() } ok = list.ChangeValue(f1, ComplexElement{i, "different value"}) if !ok { t.Fail() } f2, ok := list.Find(ComplexElement{i, ""}) if !ok { t.Fail() } if f2.GetValue().(ComplexElement).S != "different value" { t.Fail() } if ok = list.ChangeValue(f2, ComplexElement{i + 5, "different key"}); ok { t.Fail() } } } func TestGetNodeCount(t *testing.T) { list := New() for i := 0; i < maxN; i++ { list.Insert(Element(i)) } if list.GetNodeCount() != maxN { t.Fail() } } func TestString(t *testing.T) { list := NewSeed(1531889620180049576) for i := 0; i < 20; i++ { list.Insert(Element(i)) } testString := ` --> [000] -> [002] -> [009] -> [010] 000: [---|001] 001: [000|002] 002: [001|003] -> [004] 003: [002|004] 004: [003|005] -> [005] 005: [004|006] -> [009] 006: [005|007] 007: [006|008] 008: [007|009] 009: [008|010] -> [010] -> [010] 010: [009|011] -> [012] -> [---] -> [---] 011: [010|012] 012: [011|013] -> [013] 013: [012|014] -> [---] 014: [013|015] 015: [014|016] 016: [015|017] 017: [016|018] 018: [017|019] 019: [018|---] --> [019] -> [013] -> [010] -> [010] ` if list.String() != testString { t.Fail() } } func TestInfiniteLoop(t *testing.T) { list := New() list.Insert(Element(1)) if _, ok := list.Find(Element(2)); ok { t.Fail() } if _, ok := list.FindGreaterOrEqual(Element(2)); ok { t.Fail() } }