Неявное преобразование при вызове 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