Неявное преобразование при вызове std:: adj_difference()

Я хотел получить вектор расстояний между соседними точками в векторе:

struct Point { double x, y, z; } 

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

Я думал, что 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; 
}

только чтобы найти, что векторы input и output должны были быть (практически) одного и того же типа, потому что 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 должен был быть удален, чтобы функция корректно работала, но я кратко пропустил детали реализации. Кратко.

Вопрос: есть способ, которым я мог бы выполнить некоторое преобразование неявно, так что мне не нужно создавать дополнительный вектор и получить вызов adjacent_difference() с помощью input_iterator и output_iterator разных value_types?

Ответ 1

Возможно, это не так аккуратно, хотя в этом конкретном случае std::transform с двумя входными последовательностями может соответствовать цели. Например:

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

Действительно, алгоритм adjacent_difference логически разбит (почему должно быть различие одного и того же времени элементов? Почему первый выходной элемент равен первому, вместо того, чтобы получить выходную последовательность на один элемент короче входного (более логичным)?

В любом случае я не понимаю, почему вы наказываете себя, используя функциональный подход с С++, где явно сложнее писать код, труднее читать, медленнее компилировать и не выполнять быстрее. Ох... и не будем говорить о том, какое сообщение об ошибке шутки вы столкнетесь, если есть какая-либо ошибка в том, что вы набираете.

Какая плохая часть

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

Вот вместо этого явное решение цикла; он требует, чтобы два включают меньше, одно определение функции меньше, а тело функции также короче.

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

Резюме:

  • размер кода короче (algtest2.cpp составляет менее 76% от algtest.cpp)
  • время компиляции лучше (для algtest2.cpp требуется менее 83% от algtest.cpp)
  • время выполнения лучше (algtest2.cpp работает менее чем на 85% от algtest.cpp)

Поэтому, по-видимому, в моей системе (не в ручном порядке) я был прав во всех точках, кроме скорости выполнения (с "возможно" ), где переход от более медленного к значительному ускорению мне приходилось называть reserve по результату массив. Даже при этой оптимизации код, конечно, короче.

Я также считаю, что тот факт, что эта версия более читабельна, также объективна, а не мнение... но я был бы счастлив оказаться ошибочным, встретив кого-то, кто может понять, что делает функциональная вещь, и что не может понять, что делает явный.

Ответ 3

Да, это можно сделать, но не легко. Я не думаю, что это стоит усилий, если вам действительно не нужно избегать копии.

Если вы действительно хотите это сделать, вы можете попробовать создать свой собственный итератор, который выполняет итерацию по 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}

Ответ 4

Возможно, это немного грязно, но вы можете просто добавить

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 обертка более чистого решения.

Ответ 5

Так как вы не используете первый элемент, возвращаемый 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.

Ответ 6

Мне нравится а) формулировка проблемы, б) сравнение времени выполнения, в) my_adjacent_difference, d) самооценка, что my_adjacent_difference может не иметь встроенных оптимизаций. Я согласен с тем, что логика adj_difference Standard С++ ограничивает приложение алгоритма и что три строковых кода цикла - это решение, с которым многие будут работать. Я повторно использую идею применить алгоритм преобразования и представить версию на С++ 11, иллюстрирующую lambdas. С наилучшими пожеланиями.

#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