How can I convert std::vector<T> to a vector of pairs std::vector<std::pair<T,T>> using an STL algorithm?

3.2k Views Asked by At

I have a vector of integers:

std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};

Given that values.size() will always be even.

I simply want to convert the adjacent elements into a pair, like this:

std::vector<std::pair<int,int>> values = { {1,2}, {3,4} , {5,6}, {7,8} ,{9,10} };

I.e., the two adjacent elements are joined into a pair.

What STL algorithm can I use to easily achieve this? Is it possible to achieve this through some standard algorithms?

Of course, I can easily write an old school indexed for loop to achieve that. But I want to know what the simplest solution could look like using rangebased for loops or any other STL algorithm, like std::transform, etc.

5

There are 5 best solutions below

1
463035818_is_not_an_ai On

I am not aware of a standard algorithm that does what you want directly (though I am not very familiar with C++20 and beyond). You can always write a loop and most loops can be expressed via std::for_each which is a standard algorithm.


As you are accumulating elements in pairs, I would give std::accumulate a try:

#include <vector>
#include <numeric>
#include <iostream>

struct pair_accumulator {
    std::vector<std::pair<int,int>> result;
    int temp = 0;
    bool set = false;
    pair_accumulator& operator+(int x){
        if (set) {
            result.push_back({temp,x});
            set = false;
        } else {
            temp = x;
            set = true;
        }
        return *this;
    }
};

int main() {
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto x = std::accumulate(values.begin(),values.end(),pair_accumulator{}).result;
    for (const auto& e : x) {
        std::cout << e.first << " " << e.second << "\n";
    }
}

Whether this is simpler than writing a plain loop is questionable admittedly.


If possible I would try to not transform the vector. Instead of accessing result[i].first you can as well use values[i*2] and similar for second. If this is not feasible the next option is to populate a std::vector<std::pair<int,int>> from the start so you don't have to do the transformation. For the first, depending on what you need in details, the following might be a start:

#include <vector>
#include <iostream>

struct view_as_pairs {
    std::vector<int>& values;

    struct proxy {
        std::vector<int>::iterator it;
        int& first() { return *it;}
        int& second() { return *(it +1); }
    };
    proxy operator[](size_t index){
        return proxy{values.begin() + index*2};
    }
    size_t size() { return values.size() / 2;}

};

int main() {
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    view_as_pairs v{values};
    for (size_t i=0; i < v.size(); ++i){
        std::cout << v[i].first() << " " << v[i].second() << "\n";
    }
}

TL;DR: Consider if you can avoid the transformation. If you cannot avoid it, it is probably cleanest to write a loop. Standard algorithms help often but not always.

7
AndyG On

I'm not sure why you would require a standard algorithm when writing it yourself is roughly 5 lines of code (plus boilerplate):

template<class T>
std::vector<std::pair<T, T>> group_pairs(const std::vector<T>& values)
{
    assert(values.size() % 2 == 0);
    auto output = std::vector<std::pair<T, T>>();
    output.reserve(values.size()/2);
    for(size_t i = 0; i < values.size(); i+=2)
        output.emplace_back(values[i], values[i+1]);
    return output;
}

And call it like so:

std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
auto result = group_pairs(values)

Live Demo

3
Caleth On

Once we have C++23's extension to <ranges>, you can get most of the way there with std::ranges::views::chunk, although that produces subranges, not pairs.

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto chunk_to_pair = [](auto chunk)
    {
        return std::pair(*chunk.begin(), *std::next(chunk.begin()));
    };
    for (auto [first, second] : values | std::ranges::views::chunk(2) | std::ranges::views::transform(chunk_to_pair))
    {
        std::cout << first << second << std::endl;
    }
}

Alternatively, you could achieve a similar result by ziping a pair of strided views

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto odds = values | std::ranges::views::drop(0) | std::ranges::views::stride(2);
    auto evens = values | std::ranges::views::drop(1) | std::ranges::views::stride(2);
    for (auto [first, second] : std::ranges::views::zip(odds, evens))
    {
        std::cout << first << second << std::endl;
    }
}

That last one can be generalised to n-tuples

template <size_t N>
struct tuple_chunk_t
{
    template <typename R, size_t... Is>
    auto impl(R && r, std::index_sequence<Is...>)
    {
        using namespace ranges::view;
        return zip(r | drop(Is) | stride(N)...);
    }
    
    template <typename R>
    auto operator()(R && r) const
    {
        return impl(std::forward<R>(r), std::make_index_sequence<N>{});
    }
    
    template <typename R>
    friend auto operator|(R && r, chunk_t)
    {
        return impl(std::forward<R>(r), std::make_index_sequence<N>{});
    }
};

template <size_t N>
constexpr tuple_chunk_t<N> tuple_chunk;
8
PaulMcKenzie On

OK, I hinted in the comments about using std::adjacent_find, so here is how you would do this.

And yes, many (even myself) considers this a hack, where we are using a tool meant for something else to make short work of solving a seemingly unrelated problem:

#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

int main()
{
   //Test data  
   std::vector<int> v = {1,2,3,4,5,6,7,8,9,10};

   // results 
   std::vector<std::pair<int,int>> result;

   // save flag 
   bool save_it = true;

   // Use std::adjacent_find 
   std::adjacent_find(v.begin(), v.end(), [&](int n1, int n2) 
      { if (save_it) result.push_back({n1,n2}); save_it = !save_it; return false; });
          
   for (auto& pr : result)
       std::cout << pr.first << " " << pr.second << "\n";
}

Output:

1 2
3 4
5 6
7 8
9 10

The way it works is we ignore the second, fourth, sixth, etc. pairs, and only save the first, third, fifth, etc. pairs. That's controlled by a boolean flag variable, save_it.

Note that since we want to process all pairs, the std::adjacent_find predicate always returns false. That's the hackish part of this solution.

0
Sedenion On

The solutions so far try to use the std::vector iterators as input to the algorithms directly. How about defining a custom iterator that returns a std::pair and has strides of 2? Creating the vector of pairs is then a one-liner that uses std::copy. The iterator effectively provides a "view" onto the original vector in terms of pairs. This also allows the use of many of the standard algorithms. The following example could also be generalized quite a bit to work with most container iterators, i.e. you do the difficult work of defining such an iterator once and then you can apply it to all sorts of containers and algorithms. Live example: https://godbolt.org/z/ceEsvKhzd

#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>

struct pair_iterator {
    using difference_type = std::vector<int>::const_iterator::difference_type;
    using value_type = std::pair<int, int>;
    using pointer = value_type*;
    using reference = value_type; // Not a pair&, but that is ok for LegacyIterator
    // Can't be forward_iterator_tag because "reference" is not a pair&
    using iterator_category = std::input_iterator_tag;

    reference operator*()const { return {*base_iter, *(base_iter + 1)}; }
    pair_iterator & operator++() { base_iter += 2; return *this; }
    pair_iterator operator++(int) { auto ret = *this; ++(*this); return ret; }

    friend bool operator==(pair_iterator lhs, pair_iterator rhs){
        return lhs.base_iter == rhs.base_iter;
    }

    friend bool operator!=(pair_iterator lhs, pair_iterator rhs){
        return lhs.base_iter != rhs.base_iter;
    }

    std::vector<int>::const_iterator base_iter{};
};

auto pair_begin(std::vector<int> const & v){ assert(v.size()%2==0); return pair_iterator{v.begin()}; }
auto pair_end(std::vector<int> const & v){ assert(v.size()%2==0); return pair_iterator{v.end()}; }

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    std::vector<std::pair<int, int>> pair_values;

    std::copy(pair_begin(values), pair_end(values), std::back_inserter(pair_values));

    for (auto const & pair : pair_values) {
        std::cout << "{" << pair.first << "," << pair.second << "} ";
    }
    std::cout << std::endl;
}