init
This commit is contained in:
124
pkg/util/heap/heap.go
Normal file
124
pkg/util/heap/heap.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2025 wangyusong
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package heap
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Heap[T any] struct {
|
||||
inner *innerHeap[T]
|
||||
limit int
|
||||
}
|
||||
|
||||
func New[T any](data []T, less func(a, b T) bool) *Heap[T] {
|
||||
h := &Heap[T]{
|
||||
inner: newInnerHeap(data, less),
|
||||
limit: cap(data),
|
||||
}
|
||||
heap.Init(h.inner)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Heap[T]) TryEvictPush(x T) {
|
||||
switch {
|
||||
case h.Len() < h.limit:
|
||||
case h.inner.less(h.Peek(), x):
|
||||
h.Pop()
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
h.Push(x)
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Push(x T) {
|
||||
heap.Push(h.inner, x)
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Pop() T {
|
||||
return heap.Pop(h.inner).(T)
|
||||
}
|
||||
|
||||
func (h *Heap[T]) PopLast() T {
|
||||
return heap.Remove(h.inner, h.Len()-1).(T)
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Peek() T {
|
||||
if h.Len() == 0 {
|
||||
var zero T
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
return h.inner.data[0]
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Len() int {
|
||||
return h.inner.Len()
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Cap() int {
|
||||
return h.limit
|
||||
}
|
||||
|
||||
func (h *Heap[T]) Slice() []T {
|
||||
return h.inner.data
|
||||
}
|
||||
|
||||
func (h *Heap[T]) DESCSort() {
|
||||
sort.Slice(h.inner.data, func(i, j int) bool {
|
||||
return !h.inner.less(h.inner.data[i], h.inner.data[j])
|
||||
})
|
||||
}
|
||||
|
||||
type innerHeap[T any] struct {
|
||||
data []T
|
||||
less func(a, b T) bool
|
||||
}
|
||||
|
||||
func newInnerHeap[T any](data []T, less func(a, b T) bool) *innerHeap[T] {
|
||||
return &innerHeap[T]{
|
||||
data: data,
|
||||
less: less,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *innerHeap[T]) Len() int {
|
||||
return len(h.data)
|
||||
}
|
||||
|
||||
func (h *innerHeap[T]) Less(i, j int) bool {
|
||||
return h.less(h.data[i], h.data[j])
|
||||
}
|
||||
|
||||
func (h *innerHeap[T]) Swap(i, j int) {
|
||||
h.data[i], h.data[j] = h.data[j], h.data[i]
|
||||
}
|
||||
|
||||
func (h *innerHeap[T]) Push(x any) {
|
||||
h.data = append(h.data, x.(T))
|
||||
}
|
||||
|
||||
func (h *innerHeap[T]) Pop() any {
|
||||
n := len(h.data)
|
||||
x := h.data[n-1]
|
||||
h.data = h.data[:n-1]
|
||||
|
||||
return x
|
||||
}
|
||||
324
pkg/util/heap/heap_test.go
Normal file
324
pkg/util/heap/heap_test.go
Normal file
@@ -0,0 +1,324 @@
|
||||
// Copyright (C) 2025 wangyusong
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package heap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/glidea/zenfeed/pkg/test"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
type givenDetail struct{}
|
||||
type whenDetail struct {
|
||||
data []int
|
||||
less func(a, b int) bool
|
||||
}
|
||||
type thenExpected struct {
|
||||
len int
|
||||
top int
|
||||
}
|
||||
|
||||
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
|
||||
{
|
||||
Scenario: "Create min heap",
|
||||
When: "creating a new min heap with initial data",
|
||||
Then: "should create a valid heap with elements in min-heap order",
|
||||
WhenDetail: whenDetail{
|
||||
data: []int{3, 1, 4, 2},
|
||||
less: func(a, b int) bool { return a < b },
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
len: 4,
|
||||
top: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Create max heap",
|
||||
When: "creating a new max heap with initial data",
|
||||
Then: "should create a valid heap with elements in max-heap order",
|
||||
WhenDetail: whenDetail{
|
||||
data: []int{3, 1, 4, 2},
|
||||
less: func(a, b int) bool { return a > b },
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
len: 4,
|
||||
top: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Create empty heap",
|
||||
When: "creating a new heap with no initial data",
|
||||
Then: "should create an empty heap",
|
||||
WhenDetail: whenDetail{
|
||||
data: []int{},
|
||||
less: func(a, b int) bool { return a < b },
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
len: 0,
|
||||
top: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Scenario, func(t *testing.T) {
|
||||
// When.
|
||||
h := New(tt.WhenDetail.data, tt.WhenDetail.less)
|
||||
|
||||
// Then.
|
||||
Expect(h.Len()).To(Equal(tt.ThenExpected.len))
|
||||
Expect(h.Peek()).To(Equal(tt.ThenExpected.top))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushPop(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
type givenDetail struct {
|
||||
heap *Heap[int]
|
||||
}
|
||||
type whenDetail struct {
|
||||
pushValue int
|
||||
}
|
||||
type thenExpected struct {
|
||||
popValue int
|
||||
newLen int
|
||||
newTop int
|
||||
}
|
||||
|
||||
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
|
||||
{
|
||||
Scenario: "Push and pop from min heap",
|
||||
Given: "a min heap with initial values",
|
||||
When: "pushing a new value and then popping",
|
||||
Then: "should maintain heap property and return the minimum value",
|
||||
GivenDetail: givenDetail{
|
||||
heap: New([]int{3, 5, 7}, func(a, b int) bool { return a < b }),
|
||||
},
|
||||
WhenDetail: whenDetail{
|
||||
pushValue: 2,
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
popValue: 2,
|
||||
newLen: 3,
|
||||
newTop: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Push and pop from max heap",
|
||||
Given: "a max heap with initial values",
|
||||
When: "pushing a new value and then popping",
|
||||
Then: "should maintain heap property and return the maximum value",
|
||||
GivenDetail: givenDetail{
|
||||
heap: New([]int{5, 3, 1}, func(a, b int) bool { return a > b }),
|
||||
},
|
||||
WhenDetail: whenDetail{
|
||||
pushValue: 8,
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
popValue: 8,
|
||||
newLen: 3,
|
||||
newTop: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Scenario, func(t *testing.T) {
|
||||
// Given.
|
||||
h := tt.GivenDetail.heap
|
||||
|
||||
// When.
|
||||
h.Push(tt.WhenDetail.pushValue)
|
||||
popValue := h.Pop()
|
||||
|
||||
// Then.
|
||||
Expect(popValue).To(Equal(tt.ThenExpected.popValue))
|
||||
Expect(h.Len()).To(Equal(tt.ThenExpected.newLen))
|
||||
Expect(h.Peek()).To(Equal(tt.ThenExpected.newTop))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeek(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
type givenDetail struct {
|
||||
heap *Heap[int]
|
||||
}
|
||||
type whenDetail struct{}
|
||||
type thenExpected struct {
|
||||
peekValue int
|
||||
unchanged bool
|
||||
}
|
||||
|
||||
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
|
||||
{
|
||||
Scenario: "Peek from min heap",
|
||||
Given: "a min heap with values",
|
||||
When: "peeking at the top element",
|
||||
Then: "should return the minimum value without modifying the heap",
|
||||
GivenDetail: givenDetail{
|
||||
heap: New([]int{2, 4, 6}, func(a, b int) bool { return a < b }),
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
peekValue: 2,
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Peek from max heap",
|
||||
Given: "a max heap with values",
|
||||
When: "peeking at the top element",
|
||||
Then: "should return the maximum value without modifying the heap",
|
||||
GivenDetail: givenDetail{
|
||||
heap: New([]int{6, 4, 2}, func(a, b int) bool { return a > b }),
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
peekValue: 6,
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Peek from empty heap",
|
||||
Given: "an empty heap",
|
||||
When: "peeking at the top element",
|
||||
Then: "should return zero value",
|
||||
GivenDetail: givenDetail{
|
||||
heap: New([]int{}, func(a, b int) bool { return a < b }),
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
peekValue: 0,
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Scenario, func(t *testing.T) {
|
||||
// Given.
|
||||
h := tt.GivenDetail.heap
|
||||
originalLen := h.Len()
|
||||
originalSlice := make([]int, len(h.Slice()))
|
||||
copy(originalSlice, h.Slice())
|
||||
|
||||
// When.
|
||||
peekValue := h.Peek()
|
||||
|
||||
// Then.
|
||||
Expect(peekValue).To(Equal(tt.ThenExpected.peekValue))
|
||||
if tt.ThenExpected.unchanged {
|
||||
Expect(h.Len()).To(Equal(originalLen))
|
||||
Expect(h.Slice()).To(Equal(originalSlice))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeapOperations(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
type givenDetail struct {
|
||||
initialData []int
|
||||
less func(a, b int) bool
|
||||
}
|
||||
type whenDetail struct {
|
||||
operations []string
|
||||
pushValues []int
|
||||
}
|
||||
type thenExpected struct {
|
||||
finalLen int
|
||||
popResults []int
|
||||
finalValues []int
|
||||
}
|
||||
|
||||
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
|
||||
{
|
||||
Scenario: "Multiple operations on min heap",
|
||||
Given: "a min heap with initial values",
|
||||
When: "performing a series of push and pop operations",
|
||||
Then: "should maintain heap property and return values in ascending order",
|
||||
GivenDetail: givenDetail{
|
||||
initialData: []int{5, 3, 7},
|
||||
less: func(a, b int) bool { return a < b },
|
||||
},
|
||||
WhenDetail: whenDetail{
|
||||
operations: []string{"push", "push", "pop", "pop", "pop", "pop"},
|
||||
pushValues: []int{1, 9},
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
finalLen: 1,
|
||||
popResults: []int{1, 3, 5, 7},
|
||||
finalValues: []int{9},
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Multiple operations on max heap",
|
||||
Given: "a max heap with initial values",
|
||||
When: "performing a series of push and pop operations",
|
||||
Then: "should maintain heap property and return values in descending order",
|
||||
GivenDetail: givenDetail{
|
||||
initialData: []int{5, 3, 7},
|
||||
less: func(a, b int) bool { return a > b },
|
||||
},
|
||||
WhenDetail: whenDetail{
|
||||
operations: []string{"push", "push", "pop", "pop", "pop", "pop"},
|
||||
pushValues: []int{1, 9},
|
||||
},
|
||||
ThenExpected: thenExpected{
|
||||
finalLen: 1,
|
||||
popResults: []int{9, 7, 5, 3},
|
||||
finalValues: []int{1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Scenario, func(t *testing.T) {
|
||||
// Given.
|
||||
h := New(tt.GivenDetail.initialData, tt.GivenDetail.less)
|
||||
|
||||
// When.
|
||||
pushIndex := 0
|
||||
popResults := []int{}
|
||||
|
||||
for _, op := range tt.WhenDetail.operations {
|
||||
switch op {
|
||||
case "push":
|
||||
if pushIndex < len(tt.WhenDetail.pushValues) {
|
||||
h.Push(tt.WhenDetail.pushValues[pushIndex])
|
||||
pushIndex++
|
||||
}
|
||||
case "pop":
|
||||
if h.Len() > 0 {
|
||||
popResults = append(popResults, h.Pop())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then.
|
||||
Expect(h.Len()).To(Equal(tt.ThenExpected.finalLen))
|
||||
Expect(popResults).To(Equal(tt.ThenExpected.popResults))
|
||||
Expect(h.Slice()).To(Equal(tt.ThenExpected.finalValues))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user