Home > Mobile >  How to convert std::vector<T> to a vector of pairs std::vector<std::pair<T,T>> usi
How to convert std::vector<T> to a vector of pairs std::vector<std::pair<T,T>> usi

Time:04-02

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 I can 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 range based for-loops or any other STL algorithm like std::transform etc.

CodePudding user response:

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;
    }
}

CodePudding user response:

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>

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.

CodePudding user response:

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.

CodePudding user response:

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

  • Related