Functional Programming in Python, Part I: Sliding Windows

software
programming
functional-programming
Author

Weyland Joyner

Published

April 8, 2026

Introduction

The default way of writing Python in the wild, particularly for data structures and algorithms style problems, involves starting with loops and conditionally updating one or more independent state variables based on multi-line control flow.

By ‘functional-lite’ I don’t mean you need to learn Haskell (though maybe we all should). In the following examples I’ll show ways of thinking about your Python programs in terms of transformations on data.

In many languages, basic FP means using map/filter/reduce in place of iterators and for loops. In Python the situation is a bit different because we have listcomps, which are very powerful and intuitive once you get used to them.

Ideally each line of your program is a self-contained declaration of a transformation on data that gets you closer to a useful output. I find this much easier to reason about and generalize than dealing with mutable state variables across many lines and for/while loops.

Fixed-Length Sliding Windows

Maximum Sum of Subarrays

Given an array of integers nums and an integer k, find the maximum sum of any contiguous subarray of size k.

Ex.

nums = [2, 1, 5, 1, 3, 2]
k = 3

First up we have a “naive” approach using listcomps and a helper function for readability:

def maximum_sum_of_subarrays(nums: list[int], k: int) -> int:
    def windows(nums: list[int], k: int) -> list[list[int]]:
        return [nums[i:i+k] for i in range(len(nums) - k + 1)]

    sums = [sum(w) for w in windows(nums, k)]
    return max(sums)

nums = [2, 1, 5, 1, 3, 2]
k = 3
print(maximum_sum_of_subarrays(nums, k))
9

One thing to note is that the number of subarrays of length k in a list nums of length n is always going to equal len(nums) - k + 1. Once you know that the solution above reads nicely but it’s suboptimal. We could express it even more concisely:

return max(sum(w) for w in (nums[i:i+k] for i in range(len(nums) - k + 1)))

But the problem is sum(w) rescans each window. So, it’s O(n*k). But look how closely it mirrors the problem statement:

find the maximum sum of any contiguous subarray of size k
max(sum(w) for w in windows(nums, k))

Alright, the solution to the compute complexity is using prefix sums.

from itertools import accumulate

def maximum_sum_of_subarrays_v2(nums: list[int], k: int) -> int:
    """Use prefix sums
    """
    prefix = [0] + list(accumulate(nums))
    # [2, 3, 8, 9, 12, 14]
    # accumulate is a scan (similar to Haskell's scanl)
    # that
    sums = [prefix[i+k] - prefix[i] for i in range(len(nums) - k + 1)]
    return max(sums)
nums = [2, 1, 5, 1, 3, 2]
k = 3
maximum_sum_of_subarrays_v2(nums, k)
9

Max Sum of Distinct Subarrays

OK now let’s do one with a condition - the subarray cannot include any duplicate values.

def maximum_unique_sum_of_subarrays(nums: list[int], k: int) -> int:
    def windows(nums: list[int], k: int) -> list[list[int]]:
        return (nums[i:i+k] for i in range(len(nums) - k + 1))

    def is_unique(window: list[int]) -> bool:
        return len(set(window)) == len(window)

    return max(sum(w) for w in windows(nums, k) if is_unique(w))
nums = [3, 2, 2, 3, 4, 6, 7, 7, -1]
k = 4
maximum_unique_sum_of_subarrays(nums, k)
20

Now let’s do this one:

Given an array of integers representing card values, write a function to calculate the maximum score you can achieve by picking exactly k cards.

You must pick cards in order from either end. You can take some cards from the beginning, then switch to taking cards from the end, but you cannot skip cards or pick from the middle.

Variable-Length Sliding Windows

Longest Substring

def longest_substring(s: str) -> int:

    def is_valid(state) -> bool:
        return max(state.values()) <= 1

    def shrink_until_valid(s: str, freq_dict: dict, start: int):
        while not is_valid(freq_dict):
            freq_dict[s[start]] -= 1
            if freq_dict[s[start]] == 0:
                del freq_dict[start]
            start += 1
        return freq_dict, start

    def window_length(s: str):
        freq_dict, start = {}, 0
        for end, ch in enumerate(s):
            freq_dict[ch] = freq_dict.get(ch, 0) + 1
            freq_dict, start = shrink_until_valid(s, freq_dict, start)
            yield end - start + 1

    return max(window_length(s), default=0)
s = "substring"
print(longest_substring(s))
8

Longest Repeating Character Substring

def longest_repeating(s: str) -> int:
    pass