неявное преобразование при вызове std ::acent_difference () - PullRequest
10 голосов
/ 25 ноября 2011

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

struct Point { double x, y, z; } 

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

Я думал, что stl::adjacent_difference() подойдет длямне, если бы я просто предоставил функцию, которая находит расстояние между 2 точками:

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?

Ответы [ 6 ]

4 голосов
/ 25 ноября 2011

Действительно, алгоритм 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$ 

Ниже приводится приемлемое решение (я исправляюd, что, очевидно, является умственной передачей вектора точек по значению).

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

Резюме:

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

Так что, очевидно, в моей системе (не отобранной) я был прав по всем пунктам, кроме скорости выполнения (с «возможно»), где можно перейти от немного медленнее к существеннобыстрее я должен был вызвать reserve в массиве результатов.Даже с этой оптимизацией код, конечно, короче.

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

2 голосов
/ 25 ноября 2011

Вероятно, это не так аккуратно, хотя, в данном конкретном случае, 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; 
}

Надеюсь, это поможет

1 голос
/ 25 ноября 2011

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

1 голос
/ 25 ноября 2011

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

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 является более чистым решением.

1 голос
/ 25 ноября 2011

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

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

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

#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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...