Skip to content

Commit 8873147

Browse files
committed
feat(data structures, streams): moving average
1 parent 4aff926 commit 8873147

4 files changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Moving Average from Data Stream
2+
3+
Given a stream of integers and a window size, calculate the moving average of all integers in the sliding window.
4+
Implement a class called MovingAverage that has the following methods:
5+
6+
Constructor (int size): This constructor initializes the object with the specified window size.
7+
8+
double next (int val): This method takes an integer value as input and returns the moving average of the last size
9+
values from the stream.
10+
11+
## Constraints
12+
13+
- 1 <= size <= 100
14+
- -10^3 <= val <= 10^3
15+
- At most 10^2 calls will be made to next
16+
17+
## Examples
18+
19+
![Example 1](./images/examples/moving_average_example_1.png)
20+
21+
## Solution
22+
23+
The algorithm calculates the moving average of the most recent values within a fixed-size window. It employs a queue to
24+
store these values and maintains a running sum for efficient computation. Each time a new value is added to the window,
25+
it is appended to the queue, and the running sum is updated accordingly. If the queue exceeds the specified size (i.e.,
26+
the window is full), the oldest value is removed from the queue, and the sum is adjusted by subtracting this value. The
27+
moving average is then calculated by dividing the running sum by the number of values in the queue. This approach ensures
28+
that the moving average is updated, achieving constant time complexity for each operation.
29+
30+
1. **Constructor**: The constructor initializes the following variables:
31+
- _size_: This represents the window size, the maximum number of recent values for calculating the moving average.
32+
- _queue_: A queue data structure stores the most recent values up to the specified window size. The queue supports
33+
efficient operations for adding new values to the end and removing the oldest value from the front, essential for
34+
maintaining the sliding window.
35+
- _window_sum_: This keeps a running sum of the values currently in the window. This allows the moving average to be
36+
calculated efficiently without the need to sum all values repeatedly.
37+
38+
2. The **next** method: The next method calculates the moving average by following these steps:
39+
- Enqueue the new value (val) to queue and add it to window_sum. This effectively extends the current sliding window
40+
to include the new value.
41+
- If the number of elements in queue exceeds size (indicating the window is full), remove the oldest value from queue
42+
and update window_sum by subtracting this value. This ensures the queue size stays within the specified window size.
43+
- Compute the moving average as window_sum / len(queue). Use floating-point division to ensure the result is a float.
44+
45+
### Time Complexity
46+
47+
1. **Constructor**: Initializing the MovingAverage instance with a given size takes O(1) time. This is because the operation
48+
involves only setting the size, initializing an empty queue, and initializing the sum.
49+
2. **Next method**:
50+
- Appending a value: Adding a value to the queue takes O(1) time as operations like append and popleft are optimized
51+
for constant time execution in a queue.
52+
- Removing the oldest value: If the window is full, removing the oldest value also takes O(1) time, as popleft is a
53+
constant time operation in a queue.
54+
- Calculating the moving average: This step involves dividing window_sum by the number of elements in the queue,
55+
which is an O(1) operation.
56+
57+
Thus the time complexity of the next method is O(1).
58+
59+
### Space Complexity
60+
61+
The queue holds up to _size_ elements at any given time (i.e., the number of elements in the sliding window). Therefore,
62+
the space complexity of the queue is O(size), where size is the maximum window size.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Deque
2+
from collections import deque
3+
4+
5+
class MovingAverage:
6+
def __init__(self, size):
7+
"""
8+
Initializes the moving average object
9+
Args:
10+
size (int): The size of the moving average
11+
"""
12+
self.queue: Deque[int] = deque()
13+
self.size: int = size
14+
self.window_sum: float = 0.0
15+
16+
def next(self, val: int) -> float:
17+
"""
18+
Adds a value to the stream and returns the moving average of the stream
19+
Args:
20+
val (int): The value to add to the stream
21+
Returns:
22+
float: The moving average of the stream
23+
"""
24+
if len(self.queue) == self.size:
25+
# remove oldest value
26+
oldest_value = self.queue.popleft()
27+
self.window_sum -= oldest_value
28+
29+
# add new value to queue
30+
self.queue.append(val)
31+
self.window_sum += val
32+
33+
# calculate average
34+
return self.window_sum / len(self.queue)
163 KB
Loading
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import unittest
2+
from typing import List, Tuple
3+
from parameterized import parameterized
4+
from datastructures.streams.moving_average import MovingAverage
5+
6+
7+
TEST_CASES = [
8+
(3, [(1, 1.00000), (10, 5.50000), (3, 4.66667), (5, 6.00000)]),
9+
(2, [(7, 7.00000), (14, 10.50000), (21, 17.50000), (28, 24.50000), (35, 31.50000)]),
10+
(3, [(5, 5.00000), (10, 7.50000), (15, 10.00000), (20, 15.00000)]),
11+
(10, [(1, 1.00000), (2, 1.50000)]),
12+
(100, [(-100, -100.00000)]),
13+
]
14+
15+
16+
class MyTestCase(unittest.TestCase):
17+
@parameterized.expand(TEST_CASES)
18+
def test_moving_average(self, size: int, data_to_expected: List[Tuple[int, float]]):
19+
moving_average = MovingAverage(size)
20+
for data, expected in data_to_expected:
21+
actual = moving_average.next(data)
22+
round(actual, 5)
23+
self.assertEqual(expected, round(actual, 5))
24+
25+
26+
if __name__ == "__main__":
27+
unittest.main()

0 commit comments

Comments
 (0)