Create a vector of pairs from a single vector in C++
Asked Answered
X

5

25

I have a single even-sized vector that I want to transform into a vector of pairs where each pair contains always two elements. I know that I can do this using simple loops but I was wondering if there is a nice standard-library tool for this? It can be assumed that the original vector always contains an even amount of elements.

Example:

vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};

vector<pair<int, int>> goal { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
Xenon answered 14/2, 2022 at 13:32 Comment(7)
@DrewDormann sry, I don't quite understand. Where do you see "two vectors"? @Darth-CodeX What I meant is that in my use-case, origin always contains an even amount of elementsXenon
@Xenon OOH now I got itLixiviate
If you made an iterator that advanced twice you could use std::transform easily enoughIliad
@DrewDormann Right, sorry about that. I changed it!Xenon
@Taekahn: But OP is looking for an existing tool, not something to write on their own.Selfconscious
@Selfconscious last time I checked, iteratiors and transform are existing std tools.Iliad
Is there a need for it to be a standard tool ready on the shelf for such a simple task ? As you can see here, a simple single for loop does the trick. Why making things complicated (as proposed answers are) when they can be so simple ?Blossom
D
21

Use Range-v3:

#include <range/v3/range/conversion.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/chunk.hpp>

using namespace ranges;
using namespace ranges::views;

int main() {
    std::vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
    std::vector<std::pair<int, int>> goal {{1, 2}, {3, 4}, {5, 6}, {7, 8}};

    auto constexpr makePairFromRangeOf2 = [](auto two){
        return std::make_pair(two.front(), two.back());
    };

    auto result = origin | chunk(2)
                         | transform(makePairFromRangeOf2)
                         | to_vector;
}

Notice that if you only have to loop on result, then you only need it to be a range, so you can leave | to_vector out, because you'll still be able to do result.begin() and result.end(), which is what makes result a range.

If you don't need the inner containers to truly be std::pairs, but your just happy with calling, say, result.front().front() instead of result.front().first, then you can leave also the transform, and just be happy with auto result = origin | chunk(2);.

You don't mention why you only want a standard solution. However consider that <ranges> is standard in C++20. Unfortunately that feature is not as powerful as pre-C++20 Range-v3 library. But it will be at some point (C++23?), I think without any doubts.

Dystopia answered 14/2, 2022 at 13:59 Comment(7)
OP asked for a "standard library tool".Selfconscious
Ooooops. I'll put a note about it.Dystopia
Hmm and you can't use std::ranges eitherEstivation
In fact, C++23 has adopted ranges::to and views::chuck, so this is actually the "standard" answer.Melchizedek
I really like this solution. The reason I asked for a standard tool is that I'm on a bigger project where it might be hard to add other libraries. I'm still going to accept this answer for now since it's pretty much what I asked for. I suppose a purely standard library approach is not possible.Xenon
godbolt.org/z/GsqWG1WW7Heinrike
@Lixiviate I like your answer and have upvoted it. However, I explicitly asked for anything that I don't have to write myself because it might already exist.Xenon
S
13

As mentioned by @康桓瑋 , if you're willing to also use the ranges-v3 library, you can use a chunk() view:

std::vector origin = {1, 2, 3, 4, 5, 6, 7, 8};
auto goal = origin | ranges::views::chunk(2) | ranges::to<std::vector>;

See it working on GodBolt.

Unlike my other answer, this will be perfectly valid language-wise.

Caveats:

  • This will make a copy of your data!
  • Likely to introduce a bunch of stuff (error strings, exception handlers etc.) into your object code.
  • The ranges library increases compilation times significantly (although perhaps less so with C++20 enabled?)
  • Not based on the standard library - but apparently chunk() and to() will be in C++23, so upto a slight syntax tweak (e.g. adding std::), this will be valid C++23 with only standard library includes.
  • The elements of goal are not std::pairs, bur rather ranges. You will need to get the first and second, or first and last, elements to make an actual pair.
Selfconscious answered 14/2, 2022 at 14:15 Comment(2)
the outcome is something different then desired type: godbolt.org/z/fYz6fGMb7 transform is missing.Heinrike
@MarekR: Mentioned that in my caveats.Selfconscious
L
6

I have a function for handling both even and odd number of elements in a vector. What it does that it takes another parameter to add a number at the end of pair. I don't think there's any standard tool/library to do so as of C++ 20, there is Range-v3 library which will be in C++ 23 which isn't released yet.

Here is the try it online link.

#include <iostream>
#include <vector>

// time complexity: O(n / 2), where `n` is the length of `my_vec`
std::vector<std::pair<int, int>> vec_to_pair(const std::vector<int> &my_vec, int odd_origin)
{
    std::vector<std::pair<int, int>> val;
    for (std::size_t i = 0; i < my_vec.size(); i += 2)
    {
        int sec_val;
        if (i < my_vec.size() - 1)
            sec_val = my_vec[i + 1];
        else if (my_vec.size() % 2 != 0)
            sec_val = odd_origin;
        else 
            break;
        int data[] = {my_vec[i], sec_val};
        val.push_back({data[0], data[1]});
    }
    return val;
}

void print(const std::vector<std::pair<int, int>> &vec)
{
    std::cout << "{ ";
    for (auto &&i : vec)
        std::cout << "{ " << i.first << ", " << i.second << " }  ";
    std::cout << " }" << std::endl;
}

int main(void)
{
    std::vector<int> vec1 = {1, 2, 3, 4, 5};    // odd
    std::vector<int> vec2 = {1, 2, 3, 4, 5, 6}; // even

    auto x1 = vec_to_pair(vec1, -1);
    auto x2 = vec_to_pair(vec2, 0);

    print(x1);
    print(x2);

    return 0;
}
Lixiviate answered 14/2, 2022 at 14:11 Comment(0)
T
0

Simple way of doing this without using the range v3 library:

template <typename T>
std::vector<std::pair<T, T>> windowed(const std::vector<T> &vec) {
    const size_t size = vec.size();
    if (size % 2 != 0) {
        throw std::exception("Vector does not contain an even amount of elements!");
    }
    
    std::vector<std::pair<T, T>> result;
    for (size_t i = 0; i < size; i = i + 2) {
        const T &left = vec.at(i);
        const T &right = vec.at(i + 1);
        result.emplace_back(left, right);
    }
    return result;
}
Taphole answered 5/7, 2022 at 7:22 Comment(0)
S
-9

The intuitive, but unfortunately invalid, way to do it

There's a quick-and-dirty approach, which will kinda-hopefully-maybe do what you asked for, and will not even copy the data at all... but the downside is that you can't be certain it will work. It relies on undefined behavior, and can thus not be recommended. I'm describing it because I believe it's what one imagines, intuitively, that we might be able to do.

So, it's about using std::span with re-interpretation of the vector data:

std::vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
auto raw_data = reinterpret_cast<std::pair<int, int>*>(origin.data());
std::span<std::pair<int, int>> goal { raw_data, origin.size()/2 };

See this on GodBolt

Caveats:

  • reinterpret_cast is "dirty". It officially results in undefined behavior, and what it does in practice depends on the compiler and platform. You can circumvent this if you also forget about std::vector's and std::pair's, and instead use a 2-dimensional std::mdspan for the result. However - std::mdspan's only enter the language in C++20.
  • Speaking of language standard, this is C++20, because of std::span. Before C++20 you can still use span's (and mdspan's), but from (popular) independent libraries.
  • Specifically, if you're on a platform where unaligned accesses are not allowed, in may be a problem to insist on there being twice-the-size-of-int values at goal.data().
Selfconscious answered 14/2, 2022 at 13:59 Comment(15)
The caveat shouldn’t be that it “often” results in undefined behaviour but rather that it does so here. The code is simply illegal: a std::pair may not alias contiguous ints.Language
@KonradRudolph: Rephrased. "illegal", however, is not an appropriate description either. The C++ police will not come arrest you for it and bring you to stand trial for your crimes.Selfconscious
With the way C++ is, I don't think it's reasonable recommending UB regardless.Perigynous
The word “illegal” is routinely used to describe C++ code that violates the standard, in particular code which exhibits UB, and it’s a completely fine description. I didn’t downvote this answer but I also don’t think it’s a good answer: UB in general is not OK. It may be OK in very specific circumstances where you know the compiler and platform inside out. In general it isn’t. I almost never know the compiler/platform well enough to be confident that I can rely on UB without messing up.Language
@PasserBy: I didn't recommend it. But - let me clarify that point.Selfconscious
@KonradRudolph: Clarified that this can't be recommended. I still think this is an important answer because of its intuitiveness.Selfconscious
I fail to see what is intuitive about reinterpret_cast.Derma
@einpoklum, why not including this answer as a tail to the other one, and deleting this? If you think the info is useful, better to put it together with the "good" answer.Dystopia
@Enlico: Because it's a different answer. Also, I don't agree with the vote count the different answers got.Selfconscious
@rubenvb: It's so intuitive, that there's an idiom in dictionary about this situation: Six of one, half a dozen of the other.Selfconscious
"Intuitive"? No. "Recommended". Certainly not. "Cute and clever". Absolutely. Downvoting because it's not a good answer to the question, but I certainly do respect the craft.Conductance
@SilvioMayolo: Deny it all you want, but it is perfectly intuitive to reinterpret a long sequence of element as a sequence of pairs of elements.Selfconscious
This is my favorite answer although ranges produce very sharp answers to the questionVillasenor
Undefined behaviour and intuitive cannot live in the same sentence. The thing you think you are using intuitively does not do what you think it should intuitively do (at least not guaranteed by the language). It's undefined behaviour for a reason: there will be cases this goes horribly wrong.Derma
@rubenvb: "The thing you think you are using intuitively does not do what you think it should intuitively do" <- Ah, but it does, on non-esoteric machines. Intuition is not about language-lawyer guarantees.Selfconscious

© 2022 - 2024 — McMap. All rights reserved.