0

Imagine I have an ordered std::vector A = {x1, x2, ..., xn} and I want to perform an operation on every subsequent pair of items, e.g. f(x1, x2); f(x2, x3); ... f(xn-1, xn); f(xn, x1).

I could iterate like I normally would, while tracking the previous item:

for (auto x : A) {
    ...
    f(previous_x, x);
    previous_x = x;
}

f(previous_x, first_x);

But is there a better way to iterate through this vector? Are there features in the language that can streamline this?

Tried the solution provided. It works, but curious to know if there is a cleaner and more concise way.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
gxiv
  • 19
  • 5
  • A hack: `std::adjacent_find(A.begin(), A.end(), [](auto x1, auto x2) { f(x1, x2); return false; })`. More about this unorhodox way of using `std::adjacent_find` [here](https://stackoverflow.com/questions/71707122/how-can-i-convert-stdvectort-to-a-vector-of-pairs-stdvectorstdpairt-t). – PaulMcKenzie Nov 16 '22 at 22:04
  • Depends on what `f` does, there is also `std::adjacent_difference` and `std::inner_product`. – Ranoiaetep Nov 17 '22 at 10:02

4 Answers4

6

Here you are

    std::vector<int> v = { 1, 2, 3, 4, 5 };

    for (std::vector<int>::size_type i = 0, n = std::size( v ); i < n; i++)
    {
        std::cout << v[i] + v[( i + 1 ) % n] << ' ';
    }
    std::cout << '\n';

the output of this code snippet is

3 5 7 9 6

You can use a similar approach.

Before the for loop you can check whether a vector contains at least two elements.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
3

Ranges provide a beautiful solution for this case:

  • get the input vector,
    [1, 2, 3, 4, 5]
  • repeat it indefinitely with repeat,
    [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], ...]
  • flatten the new view out with join,
    [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...]
  • take as many elements as the vector size plus one with take, and
    [1, 2, 3, 4, 5, 1]
  • apply a transformation to each adjacent pair with adjacent_transform<2>.
    [[1, 2], [2, 3], ...] -> [f(1,2), f(2,3), ...]

Notice repeat and adjacent_transform will be available in C++23.
join and take should be available in C++20.

[Demo]

#include <fmt/ranges.h>
#include <functional>  // multiplies, plus
#include <ranges>
#include <vector>

template <typename C, typename F>
auto my_adjacent_transform(C&& c, F&& f) {
    return std::views::repeat(std::forward<C>(c))
        | std::views::join
        | std::views::take(c.size() + 1)
        | std::views::adjacent_transform<2>(f);
}

int main() {
    std::vector<int> v{ 1, 2, 3, 4, 5 };
    fmt::print("v: {}\n", v);
    fmt::print("Adding pairs: {}\n", my_adjacent_transform(v, std::plus<>{}));
    fmt::print("Multiplying pairs: {}\n", my_adjacent_transform(
        std::vector<int>{ 1, 2, 3, 4, 5 }, std::multiplies<>{}));
}

// Outputs:
//
//   v: [1, 2, 3, 4, 5]
//   Adding pairs: [3, 5, 7, 9, 6]
//   Multiplying pairs: [2, 6, 12, 20, 5]

Alternatively, you could already use Eric Niebler's range-v3 library, and the solution would be quite similar:

  • get the input vector,
    [1, 2, 3, 4, 5]
  • repeat its contents indefinitely with cycle,
    [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...]
  • take as many elements as the vector size plus one with take,
    [1, 2, 3, 4, 5, 1]
  • create a view of adjacent pairs with sliding(2), and
    [[1, 2], [2, 3], ...]
  • apply a transformation to each pair with transform.
    [t(1, 2), t(2, 3), ...]

Notice from the example below that range-v3 library lets you construct a container from a view via ranges::to. This conversion function will also be part of C++23.

[Demo]

#include <fmt/ranges.h>
#include <functional>  // multiplies, plus
#include <range/v3/all.hpp>
#include <vector>

template <typename C, typename F>
auto my_adjacent_transform(C&& c, F&& f) {
    auto t = [&f](auto&& p) { return std::forward<F>(f)(p[0], p[1]); };
    return std::forward<C>(c)
        | ranges::views::cycle
        | ranges::views::take(c.size() + 1)
        | ranges::views::sliding(2)
        | ranges::views::transform(t);
}

int main() {
    std::vector<int> v{ 1, 2, 3, 4, 5 };
    fmt::print("v: {}\n", v);
    fmt::print("Adding pairs: {}\n", my_adjacent_transform(v, std::plus<>{}));
    auto w{ my_adjacent_transform(v, std::multiplies<>{})
        | ranges::to<std::vector<int>>() };
    fmt::print("Multiplying pairs: {}\n", w);
}
rturrado
  • 7,699
  • 6
  • 42
  • 62
  • You can also use [`views::adjacent_transform<2>(f)`](https://en.cppreference.com/w/cpp/ranges/adjacent_transform_view) instead of `sliding(2) | transform([&f](auto&& p){return std::forward(f)(p[0], p[1]);})` – Ranoiaetep Nov 17 '22 at 10:15
  • Also while we don't have `cycle`, we can use `repeat(c) | join` – Ranoiaetep Nov 17 '22 at 10:30
  • @Ranoiaetep Awesome. Many thanks for your comment. I've updated the answer to include in first place a code using standard ranges. I knew about `adjacent_transform` and `join`, and I had read about `repeat`, but I hadn't thought about combining `repeat(c) | join`. – rturrado Nov 17 '22 at 16:02
2

You could use an old-school non range based for-loop and dereference the current + next modulus A.size() iterator:

#include <iostream>
#include <vector>

void foo(int a, int b) { std::cout << a << ',' << b << '\n'; }

int main() {
    std::vector<int> A{1, 2, 3};

    if (A.size() >= 2) {
        for (auto it = A.begin(); it != A.end(); ++it) {
            foo(*it, *std::next(A.begin(),
                                (std::distance(A.begin(), it) + 1) % A.size()));
        }
    }
}

Output:

1,2
2,3
3,1

Or... use indices to do the same thing, like @Vlad showed.


Another option that would work with any type of container and iterators could be to save the first value that you get from dereferencing the initial iterator and reuse that when the loop ends.

Example:

#include <iostream>
#include <vector>

template <class It, class Func>
void do_pairwise(It first, It last, Func&& func) {
    if (first == last) return;

    auto curr = *first;
    auto save = curr;  // save the value for later
    for (++first; first != last; curr = *first, ++first) {
        func(curr, *first);
    }
    func(curr, save);  // reuse the first value
}

int main() {
    std::vector<int> A{1, 2, 3};

    if (A.size() >= 2) {
        do_pairwise(A.begin(), A.end(),
                    [](int a, int b) { std::cout << a << ',' << b << '\n'; });
    }
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • won't be efficient for containers that don't support non-random access iterators. what is a vector today may not be a vector tomrrow. – MarkB Nov 17 '22 at 01:52
  • @MarkB No, unlike when using the subscript operator, my first example would actually compile but be rather slow. One may want to use the subscript operator instead to get a compilation error and be forced to rethink the algorithm in case the container type changes. A container and iterator agnostic version could save the first value for later. I added that as an example. – Ted Lyngmo Nov 17 '22 at 17:53
1

Just do it:

A.push_back(A[0]); // copy first element to end
for (int j = 0; j < A.size() - 1; ++j)
    f(A[j], A[j+1]);
Pete Becker
  • 74,985
  • 8
  • 76
  • 165