Introduction
Some learning resources for the Sliding Window pattern
Real-world problems
Many problems in the real world share the sliding window pattern. For example:
- Telecommunications: Find the maximum number of users connected to a cellular network’s base station in every milliseconds sliding window.
- E-commerce: Given a dataset of product IDs in the order they were viewed by the user, and a list of products that are likely to be similar, find how many times these products occur together in the dataset.
- Video streaming: Given a stream of numbers representing the number of buffering events in a given user session, calculate the median number of buffering events in a one-minute interval.
- Social media content mining: Given the lists of topics that two users have posted about, find the shortest sequence of posts by one user that includes all the topics that the other user has posted about.
Dry-run templates
This is the better, more efficient, solution suggested in the lesson:
def find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:result.append(windowSum / K) # calculate the averagewindowSum -= arr[windowStart] # subtract the element going outwindowStart += 1 # slide the window aheadreturn resultdef main():result = find_averages_of_subarrays(5, [1, 3, 2, 6, -1, 4, 1, 8, 2])print("Averages of subarrays of size K: " + str(result))main()
To help students understand how the solution works, they may be encouraged to run through the provided solution with the following sample inputs:
Sample Input #1
K=5, Array: {1, 3, 2, 6, -1, 4, 1}
Iteration number | Line number | windowStart | windowEnd | windowSum | result[] |
- | 2 - 3 | 0 | - | 0.0 | [] |
1 | 4 - 5 | 0 | 0 | 1.0 | [] |
2 | 4 - 5 | 0 | 1 | 4.0 | [] |
3 | 4 - 5 | 0 | 2 | 6.0 | [] |
4 | 4 - 5 | 0 | 3 | 12.0 | [] |
5 | 4 - 5 | 0 | 4 | 11.0 | [] |
5 | 7 - 10 | 1 | 4 | 10.0 | [2.2] |
6 | 4 - 5 | 1 | 5 | 14.0 | [2.2] |
6 | 7 - 10 | 2 | 5 | 11.0 | [2.2, 2.8] |
7 | 4 - 5 | 2 | 6 | 12.0 | [2.2, 2.8] |
7 | 7 - 10 | 3 | 6 | 10.0 | [2.2, 2.8, 2.4] |
Sample Input #2
K=2, Array: {20, 0, 0, -1, 0, 5, 10, -10}
Iteration number | Line number | windowStart | windowEnd | windowSum | result[] |
- | 2 - 3 | 0 | - | 0 | [] |
1 | 4 - 5 | 0 | 0 | 20.0 | [] |
2 | 4 - 5 | 0 | 1 | 20.0 | [] |
2 | 7 - 10 | 1 | 1 | 0.0 | [10.0] |
3 | 4 - 5 | 1 | 2 | 0.0 | [10.0] |
3 | 7 - 10 | 2 | 2 | 0.0 | [10.0, 0.0] |
4 | 4 - 5 | 2 | 3 | -1.0 | [10.0, 0.0] |
4 | 7 - 10 | 3 | 3 | -1.0 | [10.0, 0.0, -0.5] |
5 | 4 - 5 | 3 | 4 | -1.0 | [10.0, 0.0, -0.5] |
5 | 7 - 10 | 4 | 4 | 0.0 | [10.0, 0.0, -0.5, -0.5] |
6 | 4 - 5 | 4 | 5 | 5.0 | [10.0, 0.0, -0.5, -0.5] |
6 | 7 - 10 | 5 | 5 | 5.0 | [10.0, 0.0, -0.5, -0.5, 2.5] |
7 | 4 - 5 | 5 | 6 | 15.0 | [10.0, 0.0, -0.5, -0.5, 2.5] |
7 | 7 - 10 | 6 | 6 | 10.0 | [10.0, 0.0, -0.5, -0.5, 2.5, 7.5] |
8 | 4 - 5 | 6 | 7 | 0.0 | [10.0, 0.0, -0.5, -0.5, 2.5, 7.5] |
8 | 7 - 10 | 7 | 7 | -10.0 | [10.0, 0.0, -0.5, -0.5, 2.5, 7.5, 0.0] |
Step-by-step solution construction
The first step in solving any problem that exhibits the sliding window property is to set up the code that iterates through the given array and considers the correct set of contiguous elements as the window in each iteration.
Question to ask learners: how many times will line 20 execute given an array of elements and a window size of ?
def printArraywithSlicedMarkers(array, sliceStart, sliceEnd, sliceOpening, sliceEnding):out = "["for i in range(len(array)):if sliceStart == i:out+=sliceOpeningsliceEnding = '»' if sliceEnd == i else ''out+=str(array[i]) + sliceEnding + ", "out = out[0:len(out) - 2]out += "]"return outdef find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):print("\nLoop index:", windowEnd)windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:print("Window starts at index", windowStart, ":", printArraywithSlicedMarkers(arr, windowStart, windowEnd, '«', '»'))windowStart += 1 # slide the window aheadreturn resultdef main():inputArray = [1, 3, 2, 6, -1, 4, 1, 8, 2]print("Window Size: 5, input array: ", inputArray);result = find_averages_of_subarrays(5, inputArray)print("\nAverages of subarrays of size K: ", str(result))main()
In the code snippet above, we use a custom method, printArrayWithSliceMarkers
, to place markers around the current sliding window in each iteration.
Though we have not yet written any code to calculate the average of each window, we already know for sure that we have the correct window in each iteration.
Let’s add some logic to calculate the average of each window. As a first step, let’s calculate the sum of the contents of the current window (see line 10):
def printArraywithSlicedMarkers(array, sliceStart, sliceEnd, sliceOpening, sliceEnding):out = "["for i in range(len(array)):if sliceStart == i:out+=sliceOpeningsliceEnding = '»' if sliceEnd == i else ''out+=str(array[i]) + sliceEnding + ", "out = out[0:len(out) - 2]out += "]"return outdef find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):print("\nLoop index:", windowEnd)windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:print("Window starts at index", windowStart, ":", printArraywithSlicedMarkers(arr, windowStart, windowEnd, '«', '»'))print("Sum of current window:", windowSum)windowStart += 1 # slide the window aheadreturn resultdef main():inputArray = [1, 3, 2, 6, -1, 4, 1, 8, 2]print("Window Size: 5, input array: ", inputArray);result = find_averages_of_subarrays(5, inputArray)print("\nAverages of subarrays of size K: ", str(result))main()
The problem here is that though we drop the first value from the window at every step, we don’t subtract its value from the current window sum. So, the sum only increases in value (at least as long as the incoming value is positive), even though, clearly, this should not be the case.
Let’s now add code (see line 22) to subtract the value dropping out of the window:
def printArraywithSlicedMarkers(array, sliceStart, sliceEnd, sliceOpening, sliceEnding):out = "["for i in range(len(array)):if sliceStart == i:out+=sliceOpeningsliceEnding = '»' if sliceEnd == i else ''out+=str(array[i]) + sliceEnding + ", "out = out[0:len(out) - 2]out += "]"return outdef find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):print("\nLoop index:", windowEnd)windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:print("Window starts at index", windowStart, ":", printArraywithSlicedMarkers(arr, windowStart, windowEnd, '«', '»'))print("Sum of current window:", windowSum)windowSum -= arr[windowStart]windowStart += 1 # slide the window aheadreturn resultdef main():inputArray = [1, 3, 2, 6, -1, 4, 1, 8, 2]print("Window Size: 5, input array: ", inputArray);result = find_averages_of_subarrays(5, inputArray)print("\nAverages of subarrays of size K: ", str(result))main()
The formula for the average of a set of values is:
So, if we simply divide the windowSum
by the size of the window (K
) , we’ll get the average of the values in the current window:
def printArraywithSlicedMarkers(array, sliceStart, sliceEnd, sliceOpening, sliceEnding):out = "["for i in range(len(array)):if sliceStart == i:out+=sliceOpeningsliceEnding = '»' if sliceEnd == i else ''out+=str(array[i]) + sliceEnding + ", "out = out[0:len(out) - 2]out += "]"return outdef find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):print("\nLoop index:", windowEnd)windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:windowAvg = windowSum/K;print("Window starts at index", windowStart, ":", printArraywithSlicedMarkers(arr, windowStart, windowEnd, '«', '»'))print("Sum of current window:", windowSum, ", Average of current window:", windowAvg)windowSum -= arr[windowStart]; # subtract the element dropping out of the windowwindowStart += 1 # slide the window aheadreturn resultdef main():inputArray = [1, 3, 2, 6, -1, 4, 1, 8, 2]print("Window Size: 5, input array: ", inputArray);result = find_averages_of_subarrays(5, inputArray)print("\nAverages of subarrays of size K: ", str(result))main()
All that remains now is to store the calculated average of each window in the result
array. We will adopt the convention that the average of a window starting at the index in the input array will be stored at the index in the result
array.
Students may be asked to predict how many averages will be stored in
result
given an input array of elements and a window of size .
def printArraywithSlicedMarkers(array, sliceStart, sliceEnd, sliceOpening, sliceEnding):out = "["for i in range(len(array)):if sliceStart == i:out+=sliceOpeningsliceEnding = '»' if sliceEnd == i else ''out+=str(array[i]) + sliceEnding + ", "out = out[0:len(out) - 2]out += "]"return outdef find_averages_of_subarrays(K, arr):result = []windowSum, windowStart = 0.0, 0for windowEnd in range(len(arr)):print("\nLoop index:", windowEnd)windowSum += arr[windowEnd] # add the next element# slide the window, we don't need to slide if we've not hit the required window size of 'k'if windowEnd >= K - 1:result.append(windowSum/K);print("Window starts at index", windowStart, ":", printArraywithSlicedMarkers(arr, windowStart, windowEnd, '«', '»'))print("Sum of current window:", windowSum, ", Average of current window:", result[windowStart])print("result: ", result)windowSum -= arr[windowStart]; # subtract the element dropping out of the windowwindowStart += 1 # slide the window aheadreturn resultdef main():inputArray = [1, 3, 2, 6, -1, 4, 1, 8, 2]print("Window Size: 5, input array: ", inputArray);result = find_averages_of_subarrays(5, inputArray)print("\nAverages of subarrays of size K: ", str(result))main()