888
888

Reputation: 3406

Create a vector of int from a vector of points with C++11

I have a simple point structure

struct mypoint
{
    int x;
    int y;
};

and a vector of mypoints

vector<mypoint> myvector;

If I want to create a vector of int containing all the coordinates of my points (i.e. x1, y1, x2, y2, x3, y3, ...), I could easily do it in the following way

vector<mypoint>::iterator pt, ptend(myvector.end());
vector<int> newvector;

for(pt=myvector.begin(); pt!=ptend; ++pt)
{
    newvector.push_back(pt->x);
    newvector.push_back(pt->y);
}

Is there a way to obtain the same result in one (or two) line(s) of code using the C++11?

Upvotes: 1

Views: 3149

Answers (6)

user2218982
user2218982

Reputation:

As reima already noted, if you only want to reference the existing sequence, it is sufficient to cast myvector.data() to int* (assuming sizeof(mypoint) == 2 * sizeof(int) holds). However, if you explicitly want a copy of the flattened sequence, you are probably better off creating a small utility function like this:

    template <typename T, typename U>
    std::vector<T> flatten(std::vector<U> const& other) {
        static_assert(std::is_trivially_copyable<U>::value,
                      "source type must be trivially copyable!");
        static_assert(std::is_trivially_copy_constructible<T>::value,
                      "destination type must be trivially copy constructible!");
        static_assert((sizeof(U) / sizeof(T)) * sizeof(T) == sizeof(U),
                      "sizeof(U) must be a multiple of sizeof(T)!");

        return std::vector<T>(reinterpret_cast<T const*>(other.data()),
           reinterpret_cast<T const*>(std::next(other.data(), other.size())));             
    }

    template <typename U>
    std::vector<typename U::value_type> flatten(std::vector<U> const& other) {
         return flatten<typename U::value_type>(other);
    }

reducing your code to

    auto newvector = flatten<int>(myvector);

or - if you equip your mypoint struct with a (STL-conforming) value_type member type - even to

    auto newvector = flatten(myvector);

Note, that this utility function is nothing more than a tweaked constructor using the inherently unsafe reinterpret_cast to convert mypoint pointers into int pointers. To get rid of the safety caveats that go along with the use of reinterpret_cast, the flatten function uses some static_assert parachutes. So, it's better to hide all this in a seprate function. Still, it uses a lot of C++11 features like auto, move construction, static_assert, type traits, std::next and vector::data() which pretty much strips down your call site code to a bare minimum.

Also, this is as efficient as it gets because the range constructor of vector will only perform the memory allocation and call uninitialized_copy, which will probably boil down to a call of memcpy for trivially copyable types.

Upvotes: 1

Drew Dormann
Drew Dormann

Reputation: 63775

Since you're using C++11, you can use the new for syntax.

vector<int> newvector;

for( const auto &pt : myvector)
{
    newvector.push_back(pt.x);
    newvector.push_back(pt.y);
}

Upvotes: 3

Mushy
Mushy

Reputation: 2645

You could use a std::pair<> in which you push the coordinates using std::make_pair and then push the std::pair<> into the vector such as:

mypoint a_point;
std::pair<int, int> point = std::make_pair(a_point.x, a_point.y);
vector<std::pair<int, int>> vec.push_back(point).

Perhaps bulky but in two lines it works well and encapsulates a point rather than separating the magnitudes of each point axis and placing them inside a std::vector.

Upvotes: 1

trojanfoe
trojanfoe

Reputation: 122391

Here's about 4 lines, using a lambda:

vector<mypoint> points;
vector<int> iv;

points.push_back(mypoint(1,2));
points.push_back(mypoint(3,4));
points.push_back(mypoint(5,6));

for_each(points.cbegin(), points.cend(),
    [&iv](const mypoint &pt) {
        iv.push_back(pt.x);
        iv.push_back(pt.y);
    });

Upvotes: 1

gongzhitaao
gongzhitaao

Reputation: 6682

steal from the post: C++ std::transform vector of pairs->first to new vector

vector<int> items;
std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               [](const std::pair<int, int>& p) { return p.first; });

Upvotes: 1

jtepe
jtepe

Reputation: 3350

std::vector<int> extractIntsFromPoints(const std::vector<mypoint>& pointVector)
{
    std::vector<int> retVector;    
    for (const auto& element : pointVector)
    {
        retVector.push_back(element.x);
        retVector.push_back(element.y);
    }
    return retVector;
}

Call this function where you need the int vector. I threw in the range-based for loop to make it extra C++11.

Upvotes: 4

Related Questions