diff --git a/heap/heap.go b/heap/heap.go index eeb1fbb..673853e 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -22,7 +22,7 @@ type Min struct { func NewMin(k int) *Min { return &Min{ K: k, - Items: make([]Item, k), + Items: make([]Item, 0, k), Index: make(map[string]int, k), } } @@ -31,7 +31,7 @@ var _ heap.Interface = &Min{} func (me Min) SizeBytes() int { structSize := sizeofMinStruct - bucketsSize := len(me.Items)*sizeofItem + me.StoredKeysBytes + bucketsSize := cap(me.Items)*sizeofItem + me.StoredKeysBytes indexSize := sizeof.StringIntMap + (sizeof.Int+sizeof.String)*len(me.Index) return structSize + bucketsSize + indexSize } @@ -127,7 +127,7 @@ func (me *Min) Update(item string, fingerprint uint32, count uint32) bool { me.StoredKeysBytes += len(item) if !me.Full() { // heap not full: add to heap - me.Push(Item{ + heap.Push(me, Item{ Count: count, Fingerprint: fingerprint, Item: item, diff --git a/heap/heap_test.go b/heap/heap_test.go new file mode 100644 index 0000000..fb669d6 --- /dev/null +++ b/heap/heap_test.go @@ -0,0 +1,180 @@ +package heap_test + +import ( + "testing" + "unsafe" + + "github.com/keilerkonzept/topk/heap" + sketchheap "github.com/keilerkonzept/topk/heap" + "github.com/keilerkonzept/topk/internal/sizeof" +) + +func TestMinHeap_LessSwap(t *testing.T) { + h := sketchheap.NewMin(5) + + h.Items = []sketchheap.Item{ + {Item: "a", Count: 5, Fingerprint: 1}, + {Item: "b", Count: 2, Fingerprint: 2}, + {Item: "c", Count: 3, Fingerprint: 3}, + } + + // Check Less function + if !h.Less(1, 0) { + t.Errorf("expected Less(1, 0) to be true") + } + if h.Less(0, 1) { + t.Errorf("expected Less(0, 1) to be false") + } + + // Check Swap function + h.Swap(0, 1) + if h.Items[0].Item != "b" || h.Items[1].Item != "a" { + t.Errorf("expected Swap to switch elements 'a' and 'b'") + } +} + +func TestMinHeap_Full(t *testing.T) { + h := sketchheap.NewMin(2) + + h.Update("a", 1, 2) + if h.Full() { + t.Errorf("expected heap to not be full") + } + + h.Update("b", 2, 2) + if !h.Full() { + t.Errorf("expected heap to be full") + } +} + +func TestMinHeap_Update(t *testing.T) { + h := sketchheap.NewMin(2) + + // Insert new item + h.Update("a", 1, 10) + if h.Items[0].Item != "a" { + t.Errorf("expected 'a' to be in the heap") + } + + // Insert more items + h.Update("b", 2, 5) + h.Update("c", 3, 8) + h.Update("d", 3, 1) + + // "b" should be removed as it has the lowest count and heap is full + if h.Contains("b") { + t.Errorf("expected 'b' to be removed from the heap") + } + // "d" should be not enter the heap as its below the Min() + if h.Contains("d") { + t.Errorf("expected 'd' to never enter the heap") + } + + // Update an existing item + h.Update("c", 3, 15) + if h.Items[0].Item != "a" || h.Items[1].Item != "c" { + t.Errorf("expected 'a' and 'c' to be in the heap after update") + } +} + +func TestMinHeap_Min(t *testing.T) { + h := sketchheap.NewMin(2) + + // Empty heap + if h.Min() != 0 { + t.Errorf("expected Min to return 0 for empty heap") + } + + // Push some items and check minimum + h.Update("a", 1, 10) + h.Update("b", 2, 5) + h.Update("c", 2, 3) + + if h.Min() != 5 { + t.Errorf("expected Min to return 5, got %d", h.Min()) + } +} + +func TestMinHeap_Reinit(t *testing.T) { + h := sketchheap.NewMin(3) + + h.Push(sketchheap.Item{Item: "a", Count: 0, Fingerprint: 1}) + h.Push(sketchheap.Item{Item: "b", Count: 2, Fingerprint: 2}) + h.Push(sketchheap.Item{Item: "c", Count: 3, Fingerprint: 3}) + + // Reinit should remove items with 0 count + h.Reinit() + if h.Len() != 2 { + t.Errorf("expected Len after Reinit to be 2, got %d", h.Len()) + } + if h.Contains("a") { + t.Errorf("expected 'a' to be removed from the heap") + } +} + +func TestMinHeap_Find(t *testing.T) { + h := sketchheap.NewMin(3) + h.Update("a", 1, 10) + + // Find existing item + idx := h.Find("a") + if idx != 0 { + t.Errorf("expected 'a' to be at index 0, got %d", idx) + } + + // Find non-existing item + idx = h.Find("b") + if idx != -1 { + t.Errorf("expected 'b' to not be found, got %d", idx) + } +} + +func TestMinHeap_Get(t *testing.T) { + h := sketchheap.NewMin(3) + h.Update("a", 1, 10) + + // Get existing item + item := h.Get("a") + if item == nil || item.Item != "a" { + t.Errorf("expected to get item 'a', got '%v'", item) + } + + // Get non-existing item + item = h.Get("b") + if item != nil { + t.Errorf("expected to get nil for non-existing item, got '%v'", item) + } +} + +func TestMinHeap_SizeBytes(t *testing.T) { + h := heap.NewMin(3) + + const ( + sizeofMinStruct = int(unsafe.Sizeof(sketchheap.Min{})) + sizeofItem = int(unsafe.Sizeof(sketchheap.Item{})) + ) + + // Initial size should only account for the struct and empty containers + expectedSize := sizeofMinStruct + 3*sizeofItem + sizeof.StringIntMap + if h.SizeBytes() != expectedSize { + t.Errorf("expected SizeBytes to be %d, got %d", expectedSize, h.SizeBytes()) + } + + h.Update("a", 1, 5) + expectedSize += len("a") + sizeof.String + sizeof.Int // Size of new item in heap + if h.SizeBytes() != expectedSize { + t.Errorf("expected SizeBytes to be %d, got %d", expectedSize, h.SizeBytes()) + } + + h.Update("b", 2, 10) + expectedSize += len("b") + sizeof.String + sizeof.Int + if h.SizeBytes() != expectedSize { + t.Errorf("expected SizeBytes to be %d, got %d", expectedSize, h.SizeBytes()) + } + + h.Update("long_string_item", 3, 15) + expectedSize += len("long_string_item") + sizeof.String + sizeof.Int + if h.SizeBytes() != expectedSize { + t.Errorf("expected SizeBytes to be %d, got %d", expectedSize, h.SizeBytes()) + } +}