for_each
adjacent elements
coding patterns
algorithm design
programming techniques

for_each that gives two or n adjacent elements

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The standard std::for_each operates on one element at a time, but many algorithms need to process pairs or windows of adjacent elements — computing differences, detecting changes, or applying sliding-window operations. C++23 added std::views::adjacent and std::views::slide for this purpose. In C++17 and earlier, use std::adjacent_find with a custom predicate, manual iterator arithmetic, or a custom for_each_adjacent function. For Python and JavaScript, the equivalent patterns use zip, itertools.pairwise, or Array.reduce.

C++23: views::adjacent and views::slide

cpp
1#include <ranges>
2#include <vector>
3#include <iostream>
4
5int main() {
6    std::vector<int> v = {1, 4, 2, 8, 5, 7};
7
8    // adjacent<2> gives pairs of consecutive elements
9    for (auto [a, b] : v | std::views::adjacent<2>) {
10        std::cout << a << ", " << b << " -> diff: " << b - a << "\n";
11    }
12    // 1, 4 -> diff: 3
13    // 4, 2 -> diff: -2
14    // 2, 8 -> diff: 6
15    // 8, 5 -> diff: -3
16    // 5, 7 -> diff: 2
17
18    // adjacent<3> gives triples
19    for (auto [a, b, c] : v | std::views::adjacent<3>) {
20        std::cout << a << ", " << b << ", " << c << "\n";
21    }
22    // 1, 4, 2
23    // 4, 2, 8
24    // 2, 8, 5
25    // 8, 5, 7
26
27    return 0;
28}

std::views::adjacent<N> produces a view of tuples containing N consecutive elements from the range. It is a compile-time fixed-size sliding window.

C++23: views::slide (Runtime Window Size)

cpp
1#include <ranges>
2#include <vector>
3#include <iostream>
4
5int main() {
6    std::vector<int> v = {1, 2, 3, 4, 5};
7
8    // slide(3) gives windows of size 3 as subranges
9    for (auto window : v | std::views::slide(3)) {
10        for (int x : window) {
11            std::cout << x << " ";
12        }
13        std::cout << "\n";
14    }
15    // 1 2 3
16    // 2 3 4
17    // 3 4 5
18
19    return 0;
20}

std::views::slide(n) produces subranges of size n, where n can be determined at runtime. Each subrange is a view into the original container.

C++17: Custom for_each_adjacent

cpp
1#include <vector>
2#include <iostream>
3
4template <typename Iter, typename Func>
5void for_each_adjacent(Iter begin, Iter end, Func func) {
6    if (begin == end) return;
7    auto prev = begin;
8    for (auto it = std::next(begin); it != end; ++it) {
9        func(*prev, *it);
10        prev = it;
11    }
12}
13
14int main() {
15    std::vector<int> v = {10, 20, 15, 30, 25};
16
17    for_each_adjacent(v.begin(), v.end(), [](int a, int b) {
18        std::cout << a << " -> " << b;
19        if (b > a) std::cout << " (increase)";
20        else std::cout << " (decrease)";
21        std::cout << "\n";
22    });
23    // 10 -> 20 (increase)
24    // 20 -> 15 (decrease)
25    // 15 -> 30 (increase)
26    // 30 -> 25 (decrease)
27
28    return 0;
29}

This is the pre-C++23 solution. The template works with any forward iterator and any binary function.

C++17: N-Element Sliding Window

cpp
1#include <vector>
2#include <iostream>
3#include <numeric>
4
5template <typename Iter, typename Func>
6void for_each_window(Iter begin, Iter end, int window_size, Func func) {
7    if (std::distance(begin, end) < window_size) return;
8    for (auto it = begin; it + window_size <= end; ++it) {
9        func(it, it + window_size);
10    }
11}
12
13int main() {
14    std::vector<int> v = {1, 3, 5, 7, 9, 11};
15
16    // Moving average with window size 3
17    for_each_window(v.begin(), v.end(), 3,
18        [](auto begin, auto end) {
19            double sum = std::accumulate(begin, end, 0.0);
20            double avg = sum / std::distance(begin, end);
21            std::cout << "avg: " << avg << "\n";
22        });
23    // avg: 3
24    // avg: 5
25    // avg: 7
26    // avg: 9
27
28    return 0;
29}

Using std::adjacent_find as a Workaround

cpp
1#include <algorithm>
2#include <vector>
3#include <iostream>
4
5int main() {
6    std::vector<int> v = {1, 3, 2, 5, 4};
7
8    // adjacent_find visits pairs — abuse it to process all pairs
9    // by always returning false
10    std::adjacent_find(v.begin(), v.end(),
11        [](int a, int b) {
12            std::cout << "pair: " << a << ", " << b << "\n";
13            return false;  // Never "find" — visits all pairs
14        });
15    // pair: 1, 3
16    // pair: 3, 2
17    // pair: 2, 5
18    // pair: 5, 4
19
20    return 0;
21}

This is a hack — std::adjacent_find is designed to find the first adjacent pair matching a predicate, but returning false forces it to visit every pair. Use the custom function template for production code.

Python: itertools.pairwise (3.10+)

python
1from itertools import pairwise
2
3data = [10, 20, 15, 30, 25]
4
5# pairwise gives consecutive pairs
6for a, b in pairwise(data):
7    diff = b - a
8    print(f"{a} -> {b}: {'up' if diff > 0 else 'down'} {abs(diff)}")
9# 10 -> 20: up 10
10# 20 -> 15: down 5
11# 15 -> 30: up 15
12# 30 -> 25: down 5
13
14# N-element sliding window
15from itertools import islice
16
17def sliding_window(iterable, n):
18    it = iter(iterable)
19    window = list(islice(it, n))
20    if len(window) == n:
21        yield tuple(window)
22    for x in it:
23        window = window[1:] + [x]
24        yield tuple(window)
25
26for window in sliding_window(data, 3):
27    print(window)
28# (10, 20, 15)
29# (20, 15, 30)
30# (15, 30, 25)

JavaScript: Adjacent Element Patterns

javascript
1const data = [10, 20, 15, 30, 25];
2
3// Process adjacent pairs
4data.forEach((val, i) => {
5  if (i === 0) return;
6  const prev = data[i - 1];
7  console.log(`${prev} -> ${val}: diff ${val - prev}`);
8});
9
10// Sliding window of size n using reduce
11function slidingWindow(arr, n) {
12  return arr.slice(0, arr.length - n + 1).map((_, i) => arr.slice(i, i + n));
13}
14
15console.log(slidingWindow(data, 3));
16// [[10,20,15], [20,15,30], [15,30,25]]
17
18// Using Array.reduce for pairwise
19const diffs = data.reduce((acc, val, i) => {
20  if (i > 0) acc.push(val - data[i - 1]);
21  return acc;
22}, []);
23console.log(diffs);  // [10, -5, 15, -5]

Common Pitfalls

  • Off-by-one with window size: A container of N elements has N-1 adjacent pairs, N-2 triples, and so on. Forgetting this leads to out-of-bounds access. Always check that the container size is at least the window size before iterating.
  • Iterator invalidation: If the callback modifies the container (inserting or erasing elements), iterators become invalid. Never modify the container during a sliding-window traversal. Copy data if mutation is needed.
  • Using std::adjacent_find for side effects: The standard does not guarantee adjacent_find processes all pairs if it finds a match. The return false hack works for visiting all pairs but is fragile and unclear. Use a custom function instead.
  • Performance with large windows: A naive sliding window copies elements for each position. For large windows, use a deque-based approach that adds one element and removes one per step, achieving O(1) per window instead of O(window_size).
  • views::adjacent requires random access: In C++23, std::views::adjacent<N> works best with random-access ranges. For forward-only ranges (like std::forward_list), it may not compile or may have degraded performance. Check the range concept requirements.

Summary

  • C++23 provides std::views::adjacent<N> (compile-time) and std::views::slide(n) (runtime) for sliding windows
  • In C++17, write a custom for_each_adjacent template using iterator pairs
  • std::adjacent_find can visit all pairs as a workaround but is not designed for this purpose
  • Python 3.10+ has itertools.pairwise() for adjacent pairs; use a generator for arbitrary window sizes
  • JavaScript uses index-based iteration or Array.slice for sliding windows
  • Always check that the container has enough elements for the requested window size

Course illustration
Course illustration

All Rights Reserved.