dev-here
dev-here

Reputation: 338

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

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.

Upvotes: 23

Views: 3608

Answers (5)

Caleth
Caleth

Reputation: 62686

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 : std::ranges::range_adaptor_closure<tuple_chunk_t<N>>
{
    template <std::ranges::viewable_range R>
    auto operator()(R && r) const
    {
        auto impl = []<size_t... Is>(R && r, std::index_sequence<Is...>)
        {
            return std::views::zip(std::forward<R>(r) | std::views::drop(Is) | std::views::stride(N)...);
        };
        return impl(std::forward<R>(r), std::make_index_sequence<N>{});
    }
};

template <size_t N>
constexpr tuple_chunk_t<N> tuple_chunk;

Coliru link

Upvotes: 22

PaulMcKenzie
PaulMcKenzie

Reputation: 35440

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.

Upvotes: 11

Sedenion
Sedenion

Reputation: 6123

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

Upvotes: 3

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122298

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.

Upvotes: 12

AndyG
AndyG

Reputation: 41090

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

Upvotes: 14

Related Questions