Skip to content

Commit 9c7db95

Browse files
authored
feat: Add running median. (#3)
* feat: Add running median. * Update docs. * Implement solution based on heaps. * Improve readability of code.
1 parent 9d58904 commit 9c7db95

File tree

6 files changed

+164
-0
lines changed

6 files changed

+164
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ codingcircle contains the examples from the YouTube Coding Cirle.
77
So far, the following exercises have been covered:
88

99
- [Remove k-th last element](./removekthlastelement/) – removing the k-th last element from a single-linked list
10+
- [Running median](./runningmedian/) – calculating the running median of a sequence of numbers
1011
- [Trie](./trie/) – implementing an autocomplete feature using a trie
1112

1213
## Running quality assurance

runningmedian/documentation.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package runningmedian provides an algorithm to calculate the running median
2+
// across a sequence of numbers.
3+
package runningmedian

runningmedian/max_heap.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package runningmedian
2+
3+
import "container/heap"
4+
5+
type MaxHeap []int
6+
7+
func NewMaxHeap() *MaxHeap {
8+
h := &MaxHeap{}
9+
heap.Init(h)
10+
return h
11+
}
12+
13+
func (h MaxHeap) Len() int {
14+
return len(h)
15+
}
16+
func (h MaxHeap) Less(i, j int) bool {
17+
return h[i] > h[j]
18+
}
19+
func (h MaxHeap) Swap(i, j int) {
20+
h[i], h[j] = h[j], h[i]
21+
}
22+
23+
func (h *MaxHeap) Push(value any) {
24+
*h = append(*h, value.(int))
25+
}
26+
27+
func (h *MaxHeap) Pop() any {
28+
oldHeap := *h
29+
valueIndex := len(oldHeap) - 1
30+
value := oldHeap[valueIndex]
31+
*h = oldHeap[:valueIndex]
32+
return value
33+
}

runningmedian/median.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package runningmedian
2+
3+
import (
4+
"container/heap"
5+
)
6+
7+
func getMedian(minHeap *MinHeap, maxHeap *MaxHeap) float64 {
8+
if minHeap.Len() > maxHeap.Len() {
9+
return float64((*minHeap)[0])
10+
}
11+
if minHeap.Len() < maxHeap.Len() {
12+
return float64((*maxHeap)[0])
13+
}
14+
return (float64((*minHeap)[0]) + float64((*maxHeap)[0])) / 2.0
15+
}
16+
17+
func add(value int, minHeap *MinHeap, maxHeap *MaxHeap) {
18+
if minHeap.Len()+maxHeap.Len() < 1 {
19+
heap.Push(minHeap, value)
20+
return
21+
}
22+
23+
median := getMedian(minHeap, maxHeap)
24+
if float64(value) > median {
25+
heap.Push(minHeap, value)
26+
} else {
27+
heap.Push(maxHeap, value)
28+
}
29+
}
30+
31+
func rebalance(minHeap *MinHeap, maxHeap *MaxHeap) {
32+
if minHeap.Len() > maxHeap.Len()+1 {
33+
root := heap.Pop(minHeap).(int)
34+
heap.Push(maxHeap, root)
35+
} else if maxHeap.Len() > minHeap.Len()+1 {
36+
root := heap.Pop(maxHeap).(int)
37+
heap.Push(minHeap, root)
38+
}
39+
}
40+
41+
func GetMedians(values []int) []float64 {
42+
minHeap := NewMinHeap()
43+
maxHeap := NewMaxHeap()
44+
45+
medians := []float64{}
46+
47+
for _, value := range values {
48+
add(value, minHeap, maxHeap)
49+
rebalance(minHeap, maxHeap)
50+
51+
median := getMedian(minHeap, maxHeap)
52+
medians = append(medians, median)
53+
}
54+
55+
return medians
56+
}

runningmedian/median_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package runningmedian_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/thenativeweb/codingcircle/runningmedian"
8+
)
9+
10+
func TestGetMedians(t *testing.T) {
11+
t.Run("single element", func(t *testing.T) {
12+
sequence := []int{23}
13+
medians := runningmedian.GetMedians(sequence)
14+
15+
assert.Equal(t, []float64{23}, medians)
16+
})
17+
18+
t.Run("two elements", func(t *testing.T) {
19+
sequence := []int{23, 42}
20+
medians := runningmedian.GetMedians(sequence)
21+
22+
assert.Equal(t, []float64{23, 32.5}, medians)
23+
})
24+
25+
t.Run("multiple elements (odd)", func(t *testing.T) {
26+
sequence := []int{23, 42, 36, 35, 31, 27, 172}
27+
medians := runningmedian.GetMedians(sequence)
28+
29+
assert.Equal(t, []float64{23, 32.5, 36, 35.5, 35, 33, 35}, medians)
30+
})
31+
32+
t.Run("multiple elements (even)", func(t *testing.T) {
33+
sequence := []int{23, 42, 36, 35, 31, 27}
34+
medians := runningmedian.GetMedians(sequence)
35+
36+
assert.Equal(t, []float64{23, 32.5, 36, 35.5, 35, 33}, medians)
37+
})
38+
}

runningmedian/min_heap.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package runningmedian
2+
3+
import "container/heap"
4+
5+
type MinHeap []int
6+
7+
func NewMinHeap() *MinHeap {
8+
h := &MinHeap{}
9+
heap.Init(h)
10+
return h
11+
}
12+
13+
func (h MinHeap) Len() int {
14+
return len(h)
15+
}
16+
func (h MinHeap) Less(i, j int) bool {
17+
return h[i] < h[j]
18+
}
19+
func (h MinHeap) Swap(i, j int) {
20+
h[i], h[j] = h[j], h[i]
21+
}
22+
23+
func (h *MinHeap) Push(value any) {
24+
*h = append(*h, value.(int))
25+
}
26+
27+
func (h *MinHeap) Pop() any {
28+
oldHeap := *h
29+
valueIndex := len(oldHeap) - 1
30+
value := oldHeap[valueIndex]
31+
*h = oldHeap[:valueIndex]
32+
return value
33+
}

0 commit comments

Comments
 (0)