◐ Off-By-One · answer catalog

python-streaming-sliding-window-max

2 answer(s)pythonpython3pythonpython3

"""Push-based variant for true streaming sources (socket, stdin, etc.)."""

📦 Source in repository (JSON)

Answer 1

The fix is a monotonic deque implementation that keeps candidate maxima strictly decreasing from front to back. The front of the deque is always the current window maximum, and each element is pushed once and popped at most once → O(n) total time and O(k) memory. Nothing is ever fully loaded: the input is an iterable consumed one element at a time, and outputs are produced with yield (or returned per-push in the class variant).

Key correctness details: 1. Expiration first — before inserting element at position i, drop front entries with index <= i - k (outside window [i-k+1, i]). This keeps the deque at exactly ≤ k entries at all times. 2. Monotonic maintenance with <= — pop back entries whose value is <= the new value. Using <= (not <) keeps the newest of equal maxima, which is always safe and handles duplicate maxima/ties across windows correctly. 3. Emit only when the window is full — i >= k - 1, yielding n - k + 1 values. 4. Eager validation — argument checking lives in a non-generator wrapper so ValueError for k < 1 fires at call time, not on first iteration.

Files written: ~/sliding_window_max.py (implementation) and ~/test_sliding_window_max.py (suite).

"""Streaming sliding-window maximum with a monotonic deque.
O(n) total time, O(k) memory; input consumed lazily, never fully materialized."""
from collections import deque


def sliding_window_max(stream, k):
    """Yield the max of every contiguous window of size k in `stream`.
    Yields max(n - k + 1, 0) values. Raises ValueError eagerly if k < 1."""
    if k < 1:
        raise ValueError("window size k must be >= 1")
    return _sliding_window_max(stream, k)


def _sliding_window_max(stream, k):
    # deque of (index, value), values strictly decreasing front->back
    dq = deque()
    for i, value in enumerate(stream):
        # 1) expire entries outside window [i-k+1, i]
        while dq and dq[0][0] <= i - k:
            dq.popleft()
        # 2) pop dominated back entries (newer and >= keeps newest equal max)
        while dq and dq[-1][1] <= value:
            dq.pop()
        dq.append((i, value))
        # 3) first window completes at i == k-1
        if i >= k - 1:
            yield dq[0][1]


class SlidingWindowMax:
    """Push-based variant for true streaming sources (socket, stdin, etc.)."""
    __slots__ = ("_k", "_dq", "_i")

    def __init__(self, k):
        if k < 1:
            raise ValueError("window size k must be >= 1")
        self._k = k
        self._dq = deque()
        self._i = 0

    def push(self, value):
        """Feed one value; return the window max once the window is full, else None."""
        while self._dq and self._dq[0][0] <= self._i - self._k:
            self._dq.popleft()
        while self._dq and self._dq[-1][1] <= value:
            self._dq.pop()
        self._dq.append((self._i, value))
        self._i += 1
        return self._dq[0][1] if self._i >= self._k else None

Example: list(sliding_window_max(iter([5, 4, 3, 2, 1]), 3)) → [5, 4, 3]; SlidingWindowMax(3) on pushes 1, 5, 3, 2 returns None, None, 5, 5.

Evidence & signatures

Ran `~/test_sliding_window_max.py` plus an additional lazy-consumption probe — **ALL TESTS PASSED** (`python3 test_sliding_window_max.py`):

- **Differential vs brute force:** 3,360 randomized cases (n ∈ 0..11, k ∈ 1..7, small value domain to force duplicates), each checked against naive `max(slice)` for **both** the generator and the push-based class → ~6,720 comparisons.
- **Adversarial orderings** (n = 100..1000, k = 7..n): strictly increasing, strictly decreasing, all-equal, alternating — including k = n.
- **Hand-checked edge cases:** `k=1`, `k=n`, `k>n` (yields nothing), single element, `[5,5,3]` k=2 → `[5,5]`, `[7,7,7,7]` k=2 → `[7,7,7]`.
- **10⁷ elements, k=1000, random ±10⁹:** produced 9,999,001 window maxima in **3.5 s** (~O(n) confirmed; each element pushed/popped at most once).
- **Memory:** `tracemalloc` peak of **1.7 KiB** while consuming 2M-element streams with k=5000 and k=500000 → memory tracks k, not n. Also verified deque occupancy is exactly k (never k+1) on a strictly decreasing stream.
- **Laziness proofs:** an *infinite* generator produced the first 5 window maxima without hanging; a counting stream showed exactly `n` elements are pulled (never n+k, never look-ahead); 100k values streamed line-by-line from a file handle with no array.
- **API guards:** `k < 1` raises `ValueError` eagerly at call time.
{"model": "deepseek-v4-flash", "problem_class": "python-streaming-sliding-window-max", "result": "passed", "tests": 3400}

Answer 2

The fix is a monotonic deque implementation that keeps candidate maxima strictly decreasing from front to back. The front of the deque is always the current window maximum, and each element is pushed once and popped at most once → O(n) total time and O(k) memory. Nothing is ever fully loaded: the input is an iterable consumed one element at a time, and outputs are produced with yield (or returned per-push in the class variant).

Key correctness details: 1. Expiration first — before inserting element at position i, drop front entries with index <= i - k (outside window [i-k+1, i]). This keeps the deque at exactly ≤ k entries at all times. 2. Monotonic maintenance with <= — pop back entries whose value is <= the new value. Using <= (not <) keeps the newest of equal maxima, which is always safe and handles duplicate maxima/ties across windows correctly. 3. Emit only when the window is full — i >= k - 1, yielding n - k + 1 values. 4. Eager validation — argument checking lives in a non-generator wrapper so ValueError for k < 1 fires at call time, not on first iteration.

Files written: ~/sliding_window_max.py (implementation) and ~/test_sliding_window_max.py (suite).

"""Streaming sliding-window maximum with a monotonic deque.
O(n) total time, O(k) memory; input consumed lazily, never fully materialized."""
from collections import deque


def sliding_window_max(stream, k):
    """Yield the max of every contiguous window of size k in `stream`.
    Yields max(n - k + 1, 0) values. Raises ValueError eagerly if k < 1."""
    if k < 1:
        raise ValueError("window size k must be >= 1")
    return _sliding_window_max(stream, k)


def _sliding_window_max(stream, k):
    # deque of (index, value), values strictly decreasing front->back
    dq = deque()
    for i, value in enumerate(stream):
        # 1) expire entries outside window [i-k+1, i]
        while dq and dq[0][0] <= i - k:
            dq.popleft()
        # 2) pop dominated back entries (newer and >= keeps newest equal max)
        while dq and dq[-1][1] <= value:
            dq.pop()
        dq.append((i, value))
        # 3) first window completes at i == k-1
        if i >= k - 1:
            yield dq[0][1]


class SlidingWindowMax:
    """Push-based variant for true streaming sources (socket, stdin, etc.)."""
    __slots__ = ("_k", "_dq", "_i")

    def __init__(self, k):
        if k < 1:
            raise ValueError("window size k must be >= 1")
        self._k = k
        self._dq = deque()
        self._i = 0

    def push(self, value):
        """Feed one value; return the window max once the window is full, else None."""
        while self._dq and self._dq[0][0] <= self._i - self._k:
            self._dq.popleft()
        while self._dq and self._dq[-1][1] <= value:
            self._dq.pop()
        self._dq.append((self._i, value))
        self._i += 1
        return self._dq[0][1] if self._i >= self._k else None

Example: list(sliding_window_max(iter([5, 4, 3, 2, 1]), 3)) → [5, 4, 3]; SlidingWindowMax(3) on pushes 1, 5, 3, 2 returns None, None, 5, 5.

Evidence & signatures

Ran `~/test_sliding_window_max.py` plus an additional lazy-consumption probe — **ALL TESTS PASSED** (`python3 test_sliding_window_max.py`):

- **Differential vs brute force:** 3,360 randomized cases (n ∈ 0..11, k ∈ 1..7, small value domain to force duplicates), each checked against naive `max(slice)` for **both** the generator and the push-based class → ~6,720 comparisons.
- **Adversarial orderings** (n = 100..1000, k = 7..n): strictly increasing, strictly decreasing, all-equal, alternating — including k = n.
- **Hand-checked edge cases:** `k=1`, `k=n`, `k>n` (yields nothing), single element, `[5,5,3]` k=2 → `[5,5]`, `[7,7,7,7]` k=2 → `[7,7,7]`.
- **10⁷ elements, k=1000, random ±10⁹:** produced 9,999,001 window maxima in **3.5 s** (~O(n) confirmed; each element pushed/popped at most once).
- **Memory:** `tracemalloc` peak of **1.7 KiB** while consuming 2M-element streams with k=5000 and k=500000 → memory tracks k, not n. Also verified deque occupancy is exactly k (never k+1) on a strictly decreasing stream.
- **Laziness proofs:** an *infinite* generator produced the first 5 window maxima without hanging; a counting stream showed exactly `n` elements are pulled (never n+k, never look-ahead); 100k values streamed line-by-line from a file handle with no array.
- **API guards:** `k < 1` raises `ValueError` eagerly at call time.
{"model": "deepseek-v4-flash", "problem_class": "python-streaming-sliding-window-max", "result": "passed", "tests": 3400}
Generated from the verified corpus · MIT licensedBack to the catalog