tags:

views:

161

answers:

1
#include <iostream>
#include <vector>
#include <iterator>

using namespace std;

struct Point
{
    int x;
    int y;
    Point(int x, int y) :
        x(x),
        y(y)
    {}
};

int main()
{
    vector<Point> points;
    points.push_back(Point(1, 2));
    points.push_back(Point(4, 6));

    vector<int> xs;

    for(vector<Point>::iterator it = points.begin();
        it != points.end();
        ++it)
    {
        xs.push_back(it->x);
    }

    copy(xs.begin(), xs.end(), ostream_iterator<int>(cout, " "));
    cout << endl;

    return 0;
}

I'm wondering how I would achieve the same result as the for loop above using an STL algorithm? I've tried a few things using for_each, but wasn't able to get it to work.

+6  A: 

You wouldn't use std::for_each, but rather std::transform (you're transforming a point into a single number.)

For example:

#include <algorithm> // transform resides here
#include <iostream>
#include <iterator>
#include <vector>

struct Point
{
    int x;
    int y;

    Point(int x, int y) :
    x(x),
    y(y)
    {
    }
};

int point_to_int(const Point& p)
{
    return p.x;
}

int main()
{
    std::vector<Point> points;
    points.push_back(Point(1, 2));
    points.push_back(Point(4, 6));

    std::vector<int> xs;
    std::transform(points.begin(), points.end(),
     std::back_inserter(xs), point_to_int);

    std::copy(xs.begin(), xs.end(),
     std::ostream_iterator<int>(std::cout, " "));

    std::cout << std::endl;

    return 0;
}

Because you know the size of the container you'll be transforming, you might get a slight performance improvement from the following. I also find it more readable:

std::vector<int> xs;
xs.reserve(points.size());
std::transform(points.begin(), points.end(),
        std::back_inserter(xs), point_to_int);

And with boost::lambda along with boost::bind:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>

struct Point
{
    int x;
    int y;

    Point(int x, int y) :
    x(x),
    y(y)
    {
    }
};

int main()
{
    using namespace boost;

    std::vector<Point> points;
    points.push_back(Point(1, 2));
    points.push_back(Point(4, 6));

    std::vector<int> xs;
    xs.reserve(points.size());
    std::transform(points.begin(), points.end(),
     std::back_inserter(xs), bind(&Point::x, lambda::_1));

    std::copy(xs.begin(), xs.end(),
     std::ostream_iterator<int>(std::cout, " "));

    std::cout << std::endl;

    return 0;
}

Removes the need to specify a function elsewhere. This keeps the code close to the calling site, and generally improves readability.

In C++0x, it will simply be:

std::transform(points.begin(), points.end(),
 std::back_inserter(xs), [](const Point& p){ return p.x; } );

(To the best of my knowledge, anyway)

GMan
I hope C++0x brings some `collection<a>.map(f: a=>b) => collection<b>` facility along with its lambdas, because it's embarrassing what you have to do right now (Boost's transform_iterator will let you initialize the vector directly)
wrang-wrang
Regarding your optimization: Wouldn't simple `xs.reserve(points.size())` suffice?
sbi
Ha, true. And it works better, what was I thinking?
GMan
@GMan: Actually, what are you thinking now?! `:)` If you only reserve instead of resizing, you again need the back inserter!
sbi
Haha, I'm glad someone is here to keep my silliness in check. Man that's bad haha.
GMan
@GMan: I had a good laugh here about your good-humored comment. Anyway, now it's an elaborate, comprehensive answer I fully agree with. +1
sbi