Skip to content

Commit 2a0cc0d

Browse files
authored
feat: Add heap sample. (#4)
1 parent 36749f9 commit 2a0cc0d

File tree

4 files changed

+294
-4
lines changed

4 files changed

+294
-4
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ codingcircle contains the examples from the YouTube Coding Cirle.
66

77
So far, the following exercises have been covered:
88

9-
- [Autocomplete](./autocomplete/) – implement an autocomplete feature using a trie
10-
- [Continous maximum](./continuousmax/) – calculate the maximum of a sliding window
11-
- [Remove k-th last element](./removekthlastelement/) – remove the k-th last element from a single-linked list
12-
- [Running median](./runningmedian/) – calculate the running median of a sequence of numbers
9+
- [Autocomplete](./autocomplete/) – implements an autocomplete feature using a trie
10+
- [Continous maximum](./continuousmax/) – calculates the maximum of a sliding window
11+
- [Heap](./heap/) – implements a heap from scratch, without using the built-in `container/heap` package
12+
- [Remove k-th last element](./removekthlastelement/) – removes the k-th last element from a single-linked list
13+
- [Running median](./runningmedian/) – calculates the running median of a sequence of numbers
1314

1415
## Running quality assurance
1516

heap/documentation.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package heap implements a min heap data structure without
2+
// using the built-in container/heap package.
3+
package heap

heap/min_heap.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package heap
2+
3+
import "errors"
4+
5+
type Item[TValue any] struct {
6+
Value TValue
7+
Priority int
8+
}
9+
10+
type MinHeap[TValue any] struct {
11+
items []Item[TValue]
12+
}
13+
14+
func New[TValue any]() *MinHeap[TValue] {
15+
return &MinHeap[TValue]{}
16+
}
17+
18+
func parentIndex(i int) int {
19+
return (i - 1) / 2
20+
}
21+
22+
func leftChildIndex(i int) int {
23+
return 2*i + 1
24+
}
25+
26+
func rightChildIndex(i int) int {
27+
return 2*i + 2
28+
}
29+
30+
func (h *MinHeap[TValue]) siftUp(index int) {
31+
for index > 0 {
32+
parent := parentIndex(index)
33+
34+
if h.items[parent].Priority > h.items[index].Priority {
35+
h.items[parent], h.items[index] = h.items[index], h.items[parent]
36+
index = parent
37+
} else {
38+
break
39+
}
40+
}
41+
}
42+
43+
func (h *MinHeap[TValue]) siftDown(index int) {
44+
left := leftChildIndex(index)
45+
right := rightChildIndex(index)
46+
smallest := index
47+
48+
if left < len(h.items) && h.items[left].Priority < h.items[smallest].Priority {
49+
smallest = left
50+
}
51+
52+
if right < len(h.items) && h.items[right].Priority < h.items[smallest].Priority {
53+
smallest = right
54+
}
55+
56+
if smallest != index {
57+
h.items[index], h.items[smallest] = h.items[smallest], h.items[index]
58+
h.siftDown(smallest)
59+
}
60+
}
61+
62+
func (h *MinHeap[TValue]) Peek() *Item[TValue] {
63+
if len(h.items) == 0 {
64+
return nil
65+
}
66+
67+
return &h.items[0]
68+
}
69+
70+
func (h *MinHeap[TValue]) IsValid() bool {
71+
for i := 1; i < len(h.items); i++ {
72+
if h.items[parentIndex(i)].Priority > h.items[i].Priority {
73+
return false
74+
}
75+
}
76+
return true
77+
}
78+
79+
func (h *MinHeap[TValue]) Insert(value TValue, priority int) {
80+
h.items = append(h.items, Item[TValue]{Priority: priority, Value: value})
81+
h.siftUp(len(h.items) - 1)
82+
}
83+
84+
func (h *MinHeap[TValue]) Remove(index int) error {
85+
if index < 0 || index >= len(h.items) {
86+
return errors.New("index out of range")
87+
}
88+
89+
h.items[index] = h.items[len(h.items)-1]
90+
h.items = h.items[:len(h.items)-1]
91+
92+
h.siftDown(index)
93+
94+
return nil
95+
}
96+
97+
func (h *MinHeap[TValue]) Update(index int, newPriority int) error {
98+
if index < 0 || index >= len(h.items) {
99+
return errors.New("index out of range")
100+
}
101+
102+
oldPriority := h.items[index].Priority
103+
h.items[index].Priority = newPriority
104+
105+
if newPriority < oldPriority {
106+
h.siftUp(index)
107+
} else {
108+
h.siftDown(index)
109+
}
110+
111+
return nil
112+
}
113+
114+
func (h *MinHeap[TValue]) Items() []Item[TValue] {
115+
return h.items
116+
}

heap/min_heap_test.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package heap_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/thenativeweb/codingcircle/heap"
8+
)
9+
10+
func TestNew(t *testing.T) {
11+
t.Run("Creates an empty heap.", func(t *testing.T) {
12+
h := heap.New[int]()
13+
14+
assert.True(t, h.IsValid())
15+
assert.Len(t, h.Items(), 0)
16+
})
17+
}
18+
19+
func TestInsert(t *testing.T) {
20+
t.Run("Inserts a value into the heap.", func(t *testing.T) {
21+
h := heap.New[int]()
22+
23+
h.Insert(23, 1)
24+
25+
assert.True(t, h.IsValid())
26+
assert.Len(t, h.Items(), 1)
27+
assert.Equal(t, []heap.Item[int]{
28+
{Value: 23, Priority: 1},
29+
}, h.Items())
30+
})
31+
32+
t.Run("Inserts multiple values into the heap.", func(t *testing.T) {
33+
h := heap.New[int]()
34+
35+
h.Insert(42, 2)
36+
h.Insert(17, 3)
37+
h.Insert(23, 1)
38+
39+
assert.True(t, h.IsValid())
40+
assert.Len(t, h.Items(), 3)
41+
assert.Equal(t, []heap.Item[int]{
42+
{Value: 23, Priority: 1},
43+
{Value: 17, Priority: 3},
44+
{Value: 42, Priority: 2},
45+
}, h.Items())
46+
})
47+
}
48+
49+
func TestRemove(t *testing.T) {
50+
t.Run("Removes a value from the heap.", func(t *testing.T) {
51+
h := heap.New[int]()
52+
53+
h.Insert(23, 1)
54+
55+
err := h.Remove(0)
56+
assert.NoError(t, err)
57+
58+
assert.True(t, h.IsValid())
59+
assert.Len(t, h.Items(), 0)
60+
})
61+
62+
t.Run("Removes multiple values from the heap.", func(t *testing.T) {
63+
h := heap.New[int]()
64+
65+
h.Insert(42, 2)
66+
h.Insert(17, 3)
67+
h.Insert(23, 1)
68+
69+
err := h.Remove(1)
70+
assert.NoError(t, err)
71+
err = h.Remove(1)
72+
assert.NoError(t, err)
73+
74+
assert.True(t, h.IsValid())
75+
assert.Len(t, h.Items(), 1)
76+
assert.Equal(t, []heap.Item[int]{
77+
{Value: 23, Priority: 1},
78+
}, h.Items())
79+
})
80+
}
81+
82+
func TestUpdate(t *testing.T) {
83+
t.Run("Updates a value in the heap.", func(t *testing.T) {
84+
h := heap.New[int]()
85+
86+
h.Insert(42, 2)
87+
h.Insert(17, 3)
88+
h.Insert(23, 1)
89+
90+
err := h.Update(2, 0)
91+
assert.NoError(t, err)
92+
93+
assert.True(t, h.IsValid())
94+
assert.Len(t, h.Items(), 3)
95+
assert.Equal(t, []heap.Item[int]{
96+
{Value: 42, Priority: 0},
97+
{Value: 17, Priority: 3},
98+
{Value: 23, Priority: 1},
99+
}, h.Items())
100+
})
101+
}
102+
103+
func TestPeek(t *testing.T) {
104+
t.Run("Returns the minimum value.", func(t *testing.T) {
105+
h := heap.New[int]()
106+
107+
h.Insert(42, 2)
108+
h.Insert(17, 3)
109+
h.Insert(23, 1)
110+
111+
assert.Equal(t, 23, h.Peek().Value)
112+
assert.Equal(t, 1, h.Peek().Priority)
113+
})
114+
115+
t.Run("Returns nil if the heap is empty.", func(t *testing.T) {
116+
h := heap.New[int]()
117+
118+
assert.Nil(t, h.Peek())
119+
})
120+
}
121+
122+
func TestComplex(t *testing.T) {
123+
t.Run("Complex test.", func(t *testing.T) {
124+
h := heap.New[int]()
125+
126+
h.Insert(27, 6)
127+
h.Insert(9, 3)
128+
h.Insert(17, 5)
129+
h.Insert(2, 1)
130+
h.Insert(12, 4)
131+
h.Insert(8, 2)
132+
133+
assert.True(t, h.IsValid())
134+
assert.Len(t, h.Items(), 6)
135+
assert.Equal(t, []heap.Item[int]{
136+
{Value: 2, Priority: 1},
137+
{Value: 9, Priority: 3},
138+
{Value: 8, Priority: 2},
139+
{Value: 27, Priority: 6},
140+
{Value: 12, Priority: 4},
141+
{Value: 17, Priority: 5},
142+
}, h.Items())
143+
144+
err := h.Remove(3)
145+
assert.NoError(t, err)
146+
147+
assert.True(t, h.IsValid())
148+
assert.Len(t, h.Items(), 5)
149+
assert.Equal(t, []heap.Item[int]{
150+
{Value: 2, Priority: 1},
151+
{Value: 9, Priority: 3},
152+
{Value: 8, Priority: 2},
153+
{Value: 17, Priority: 5},
154+
{Value: 12, Priority: 4},
155+
}, h.Items())
156+
157+
err = h.Update(3, -1)
158+
assert.NoError(t, err)
159+
160+
assert.True(t, h.IsValid())
161+
assert.Len(t, h.Items(), 5)
162+
assert.Equal(t, []heap.Item[int]{
163+
{Value: 17, Priority: -1},
164+
{Value: 2, Priority: 1},
165+
{Value: 8, Priority: 2},
166+
{Value: 9, Priority: 3},
167+
{Value: 12, Priority: 4},
168+
}, h.Items())
169+
})
170+
}

0 commit comments

Comments
 (0)