调用std :: adjacent_difference()时的隐式转换

时间:2011-11-25 10:36:58

标签: c++ stl

我想在矢量中得到相邻之间的距离矢量:

struct Point { double x, y, z; } 

vector<double> adjacent_distances( vector<Point> points ) {
    ...
}

如果我只提供一个找到2点之间距离的函数,我认为stl::adjacent_difference()会为我做的伎俩:

double point_distance( Point a, Point b ) {
     return magnitude(a-b);  // implementation details are unimportant 
}

因此,我希望这会奏效,

vector<double> adjacent_distances( vector<Point> points ) 
{
    vector<double> distances;

    std::adjacent_difference( points.begin(), points.end(), 
                        std::back_inserter(distances), 
                        ptr_fun( point_distance ) );
    return distances; 
}

只发现inputoutput向量必须属于(实际上)同一类型,因为adjacent_difference()调用

 output[0] = input[0];            // forces input and output to be of same value_type 
 output[1] = op( input[1], input[0] ); 
 output[2] = op( input[2], input[1] ); 
 ....
遗憾的是,这与std::adjacent_find()的工作方式不一致。

所以,我必须将我的代码转换为

double magnitude( Point pt );
Point  difference( Point a, Point b ); // implements b-a

vector<double> adjacent_distances( vector<Point> points ) 
{
    vector<Point> differences;

    std::adjacent_difference( points.begin(), points.end(), 
                        std::back_inserter(differences), 
                        ptr_fun( point_difference ) ); 


    vector<double> distances;

    std::transform( differences.begin(), differences.end(),
                        std::back_inserter(distances), 
                        ptr_fun( magnitude ) );

    return distances; 
}

NB :为了使函数正常运行,必须删除differences的第一个元素,但为简洁起见,我跳过了实现细节。

问题:有一种方法我可以隐式地实现一些转换,这样我就不必创建额外的向量,并用{{1来实现对adjacent_difference()的调用不同input_iterator的{​​}和output_iterator

6 个答案:

答案 0 :(得分:4)

确实adjacent_difference算法在逻辑上被破坏了(为什么应该是元素同一时间的差异?为什么第一个输出元素等于第一个输出元素而不是让输出序列比一个项目短一个输入一个(更合乎逻辑的方式)?

无论如何,我不明白你为什么要通过使用C ++的函数方法来惩罚自己,显然代码将更难编写,更难阅读,编译速度更慢,执行速度更快。哦..如果您输入的内容有任何错误,请不要谈论您将要面对的笑话错误消息。

的不好之处是什么?
std::vector<double> distances;
for (int i=1,n=points.size(); i<n; i++)
    distances.push_back(magnitude(points[i] - points[i-1]));

这更短,更易读,编译速度更快,执行速度更快。

修改

我想检查我的主观“更短,更易读,编译速度更快,执行速度更快”。结果如下:

~/x$ time for i in {1..10}
>   do
>     g++ -Wall -O2 -o algtest algtest.cpp
>   done

real    0m2.001s
user    0m1.680s
sys 0m0.150s
~/x$ time ./algtest

real    0m1.121s
user    0m1.100s
sys 0m0.010s
~/x$ time for i in {1..10}
>   do
>     g++ -Wall -O2 -o algtest2 algtest2.cpp
>   done

real    0m1.651s
user    0m1.230s
sys 0m0.190s
~/x$ time ./algtest2

real    0m0.941s
user    0m0.930s
sys 0m0.000s
~/x$ ls -latr algtest*.cpp
-rw-r--r-- 1 agriffini agriffini  932 2011-11-25 21:44 algtest2.cpp
-rw-r--r-- 1 agriffini agriffini 1231 2011-11-25 21:45 algtest.cpp
~/x$ 

以下是已接受的解决方案(我修复了显然是按值传递点向量的大脑)。

// ---------------- algtest.cpp -------------
#include <stdio.h>
#include <math.h>
#include <functional>
#include <algorithm>
#include <vector>

using std::vector;
using std::ptr_fun;

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

    Point operator-(const Point& other) const
    {
        return Point(x - other.x, y - other.y);
    }
};

double magnitude(const Point& a)
{
    return sqrt(a.x*a.x + a.y*a.y);
}

double point_distance(const Point& a, const Point& b)
{
    return magnitude(b - a);
}

vector<double> adjacent_distances( const vector<Point>& points ) {
    if ( points.empty() ) return vector<double>();

    vector<double> distances(
      1, point_distance( *points.begin(), *points.begin() ) );

    std::transform( points.begin(), points.end() - 1,
                    points.begin() + 1,
                    std::back_inserter(distances),
                    ptr_fun( point_distance ) );
    return distances;
}

int main()
{
    std::vector<Point> points;
    for (int i=0; i<1000; i++)
        points.push_back(Point(100*cos(i*2*3.141592654/1000),
                               100*sin(i*2*3.141592654/1000)));

    for (int i=0; i<100000; i++)
    {
        adjacent_distances(points);
    }

    return 0;
}

这是显式循环解决方案;它需要两个包括less,一个函数定义少,函数体也更短。

// ----------------------- algtest2.cpp -----------------------
#include <stdio.h>
#include <math.h>

#include <vector>

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

    Point operator-(const Point& other) const
    {
        return Point(x - other.x, y - other.y);
    }
};

double magnitude(const Point& a)
{
    return sqrt(a.x*a.x + a.y*a.y);
}

std::vector<double> adjacent_distances(const std::vector<Point>& points)
{
    std::vector<double> distances;
    if (points.size()) distances.reserve(points.size()-1);
    for (int i=1,n=points.size(); i<n; i++)
        distances.push_back(magnitude(points[i] - points[i-1]));
    return distances;
}

int main()
{
    std::vector<Point> points;
    for (int i=0; i<1000; i++)
        points.push_back(Point(100*cos(i*2*3.141592654/1000),
                               100*sin(i*2*3.141592654/1000)));

    for (int i=0; i<100000; i++)
    {
        adjacent_distances(points);
    }

    return 0;
}

要点:

  1. 代码大小更短(algtest2.cpp小于algtest.cpp的76%)
  2. 编译时间更好(algtest2.cpp需要少于algtest.cpp的83%)
  3. 执行时间更好(algtest2.cpp的运行时间不到algtest.cpp的85%)
  4. 显然在我的系统上(没有亲自挑选)我在所有点上都是正确的,除了执行速度(带有“可能”的那个)从哪里稍微慢到快得多我必须打电话给reserve结果数组。即使进行了这种优化,代码当然也会更短。

    我也认为这个版本更具可读性这一事实也是客观的而不是意见......但我很高兴能够通过遇到能够理解功能性事物正在做什么而不能做什么的人来证明是错误的。了解明确的人正在做什么。

答案 1 :(得分:2)

可能这不是那么整洁,在这种特殊情况下,std::transform 有2个输入序列可能符合目的。 例如:

vector<double> adjacent_distances( vector<Point> points ) {
    if ( points.empty() ) return vector<double>();

    vector<double> distances(
      1, point_distance( *points.begin(), *points.begin() ) );

    std::transform( points.begin(), points.end() - 1,
                    points.begin() + 1,
                    std::back_inserter(distances), 
                    ptr_fun( point_distance ) );
    return distances; 
}

希望这有帮助

答案 2 :(得分:1)

是的,这可以做到,但不容易。我不认为这是值得的,除非你真的需要避免副本。

如果你真的想这样做,你可以尝试创建自己的迭代器,迭代vector<Point>Point的包装器。

迭代器类将取消引用包装类的实例。包装器类应该支持operator -或你的距离函数,它应该存储距离。然后,您应该实现一个隐式转换为double的运算符,当adjacent_difference尝试将包装器分配给vector<double>时,将调用该运算符。

我没有时间详细说明,所以如果有什么不清楚的地方,我会稍后再回来查看,或者其他人可以尝试更好地解释。下面是一个执行此操作的包装器的示例。

 struct Foo { 
     Foo(double value) { d = value; } 
     operator double() { return d; } 
     double d; 
 };

 Foo sub(const Foo& a, const Foo& b) {
     return Foo(a.d - b.d);
 }

 vector<Foo> values = {1, 2, 3, 5, 8}; 
 vector<double> dist; 
 adjacent_difference(values.begin(), values.end(), back_inserter(dist), sub);

 // dist = {1, 1, 1, 2, 3}

答案 3 :(得分:1)

这可能有点脏,但你可以简单地添加

struct Point {
    double x,y,z;
    operator double() { return 0.0; }
};

或者

struct Point {
    double x,y,z;
    operator double() { return sqrt(x*x + y*y + z*z); } // or whatever metric you are using
};

效果是将第一个距离设置为0,或者第一个点距离原点的距离。但是,我可以想象你不希望污染你的Point结构,而是转换为double的相当任意的定义 - 在这种情况下dauphic的包装是一个更清洁的解决方案。

答案 4 :(得分:1)

由于您没有使用adjacent_difference返回的第一个元素,这正是一个给您带来麻烦的元素,您可以编写自己的算法版本,跳过该初始分配:

template <class InputIterator, class OutputIterator, class BinaryOperation>
OutputIterator my_adjacent_difference(InputIterator first, InputIterator last,
                                      OutputIterator result,
                                      BinaryOperation binary_op)
{
  if (first != last)
  {
    InputIterator prev = first++; // To start
    while (first != last)
    {
      InputIterator val = first++;
      *result++ = binary_op(*val, *prev);
      prev = val;
    }
  }
  return result;
}

这应该有效,但你会遗漏一些STL优化。

答案 5 :(得分:0)

我喜欢a)问题的表述,b)执行时间的比较,c)my_adjacent_difference,d)my_adjacent_difference可能缺乏内置优化的自我评论。我同意标准C ++ adjacent_difference逻辑限制了算法的应用程序,并且三行循环代码是一个解决方案,许多人会这样做。我重用这个想法来应用算法转换,并在C ++ 11中展示说明lambda的版本。问候。

#include <iostream>             /* Standard C++ cout, cerr */
#include <vector>               /* Standard C++ vector */
#include <algorithm>            /* Standard C++ transform */
#include <iterator>             /* Standard C++ back_inserter */
#include <cmath>                /* Standard C++ sqrt */
#include <stdexcept>            /* Standard C++ exception */
using namespace std;            /* Standard C++ namespace */

struct Point {double x, y, z;}; // I would define this differently.

int main(int, char*[])
{
    try {
        const Point     points[] = {{0, 0, 0}, {1, 0, 0}, {1, 0, 3}};
        vector<double>  distances;
        transform(points + 1, points + sizeof(points) / sizeof(Point),
            points, back_inserter(distances),
            [](const Point& p1, const Point& p2)
            {
                double  dx = p2.x - p1.x;
                double  dy = p2.y - p1.y;
                double  dz = p2.z - p1.z;
                return  sqrt(dx * dx + dy * dy + dz * dz);
            });
        copy(distances.begin(), distances.end(),
            ostream_iterator<double>(cout, "\n"));
    }
    catch(const exception& e) {
        cerr    << e.what() << endl;
        return  -1;
    }
    catch(...) {
        cerr    << "Unknown exception" << endl;
        return  -2;
    }
    return  0;
}

输出:

1
3