暗黙の変換はstd :: adjacent_difference()

私はベクトル内の隣接する points の間に距離のベクトルを得たいと思っていました:

struct Point { double x, y, z; } 

vector adjacent_distances( vector points ) {
    ...
}

私は、 stl :: adjacent_difference() が私は単純に2点間の距離を求める関数を提供していれば私に:

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

したがって、私はこれがうまくいくことを期待していました。

vector adjacent_distances( vector points ) 
{
    vector distances;

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

only to find that input and output vectors had to be of (practically) the same type because adjacent_difference() calls

 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 adjacent_distances( vector points ) 
{
    vector differences;

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


    vector distances;

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

    return distances; 
}

NB: the first element of differences had to be removed for the function to behave correctly, but I skipped the implementation details, for brevity.

Question: is there a way I could achieve some transformation implicitly, so that I don't have to create the extra vector, and achieve a call to adjacent_difference() with input_iterator and output_iterator of different value_types ?

10
私は、 std :: adjacent_difference が私が思っていたものとは違うアルゴリズムを実装していることを認識させるために私から+1しました(アルゴリズムによって生成される範囲は入力範囲よりも短い要素...)。
追加された 著者 Frerich Raabe,
問題の説明が与えられたら、おそらく、 std :: vector を値ではなくconst refで渡したいと思うでしょう。
追加された 著者 6502,

6 答え

実際に adjacent_difference アルゴリズムは論理的に壊れています(要素の同じ時刻の違いはどうしてですか?なぜ出力シーケンスを最初のものよりも1つ短い項目にするのはなぜですか? 1つを入力する(論理的に)

とにかく、なぜコードが書くのがより難しく、読みにくく、コンパイルが遅く、実行が速くないのか、C ++での機能的なアプローチを使って自分自身を罰する理由を理解できません。ああ..あなたが入力したものに何らかのエラーがあった場合に直面するであろうジョークのエラーメッセージについて話しません。

悪い部分は何ですか?

std::vector distances;
for (int i=1,n=points.size(); i

これは、より短く、より読みやすく、コンパイルが速く、さらに高速に実行することができます。

EDIT

私は自分の主観的を「短く、読みやすく、コンパイルするのが速く、実行が速いかもしれない」ことを確認したかったのです。ここでの結果:

~/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 
#include 
#include 
#include 
#include 

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 adjacent_distances( const vector& points ) {
    if ( points.empty() ) return vector();

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

代わりに、明示的なループ解決策です。 2つの関数の定義を少なくする必要があり、関数の本体も短くなります。

// ----------------------- algtest2.cpp -----------------------
#include 
#include 

#include 

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 adjacent_distances(const std::vector& points)
{
    std::vector distances;
    if (points.size()) distances.reserve(points.size()-1);
    for (int i=1,n=points.size(); i 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%未満で実行されます)

私のシステム上では明らかに、実行速度(「多分」のもの)を除いてすべての点で正しく機能していましたが、少し遅くからかなり高速になるように reserve 結果の配列この最適化を行っても、もちろんコードは短くなります。

私はまた、このバージョンがより読みやすいという事実も客観的で意見ではないと思っています...しかし、機能的なことが何をしているのかを理解することができ、明示的な方が代わりにやっています。

4
追加された
@FrerichRaabe私はあなたのコメントにほとんど同意しますが、実際には "ループボディの中に深く隠されていません"と言ってください!誰かがforループが何を理解していない場合、彼は意図された聴衆の外に、とにかくです。
追加された 著者 Christian Rau,
@ 6502:実際、私は間違っています、ごめんなさい - points が空であればバグはありません。
追加された 著者 Frerich Raabe,
また、このループが正しくないことにも注意してください。 points が空の場合は頻繁に反復処理されます。
追加された 著者 Frerich Raabe,
-1私からの主観的な "より短い、より読みやすく、コンパイルするのが速く、さらに高速に実行するかもしれません。それを証明するための数字を提供することはありません。 for ループでは、この動作はループ本体の内部に深く隠されていますが、実際には機能的なバージョンを読みやすくするために、実際には何が起こっているのかを伝えています(隣接するopintの違いを計算しています)。
追加された 著者 Frerich Raabe,
@Christian Rau:もちろん、人々は 'for'ループが何をしているのか知っていますが、 this ループが何をしているのかは分からないのでここでは十分ではありません。 'for'は何らかの反復が行われることを示唆していますが、ループの本質(隣接する相違を計算します)は真ん中に隠れています。
追加された 著者 Frerich Raabe,
@GrimFandango:この回答のコードにはバグはありません...ポイントの空のベクトルや1点だけのベクトルでも正しいことが実行されます(出力は空のベクトルになります)。
追加された 著者 6502,
@FrerichRaabe:このコードは経験からコンパイルするのに時間がかかります( #include は必要ありません)、実行速度も同じです。より短くは主観的ではない...そして正直なところ、機能的なものを読むことができる人は誰でもこの明示的なものを読むことができると思う(その逆は真実ではない)。 C ++(1xより前)の問題は、内側の操作を強制的に(ローカル構造をテンプレートで使用することはできません)強制されていることです。トップレベルよりも使用されている場所に近いほうがはるかに優れています。また、点が空であるか要素が1つしかない場合でもループは正しいです。
追加された 著者 6502,
@ 6502、私はあなたの議論を理解することが難しいコードをコンパイルエラーを通過することです。しかし、このコメントが書かれる頃には、 for ループを書くための2つの提案があり、@Freirichが指摘したように、すでにそれらのすべてにバグが隠されています。 STL関数を(再)使用する方がはるかに安全です。もちろん、私はそれが adjacent_difference()の問題を引き起こしたので、それについても議論することができます:)
追加された 著者 Grim Fandango,

おそらく、この特定のケースでは、 std :: transform 2つの入力シーケンスが目的を満たすかもしれない。 例えば:

vector adjacent_distances( vector points ) {
    if ( points.empty() ) return vector();

    vector 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
追加された
@FrerichRaabeさて、それを前に置く。しかし、それは、追加のコピーを持っているか、点の周りにあいまいなラッパーイテレータを書いている(明らかに意図を示していない)よりも優れている。
追加された 著者 Christian Rau,
+1シンプルで働きます。 std :: adjacent_difference を独断的に使用する必要はありません。
追加された 著者 Christian Rau,
実際にはシンプルで壊れています。 points が空の場合、正しく動作しません。
追加された 著者 Frerich Raabe,
@FrerichRaabe:おっと、指摘ありがとう!答えを編集しました。今回は正しいと思っています。
追加された 著者 Ise Wisteria,
@ 6502:(それは私のコメントだと仮定して)OPのコードから関数の署名をコピー&ペーストしました。
追加された 著者 Ise Wisteria,

これはちょっと汚いかもしれませんが、

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 に変換するための任意の定義で汚染したくないことが想像できます。この場合、ドフィーフのラッパーはよりクリーンなソリューションです。

1
追加された
私はポイントラッパーを作成し、演算子double()を実装することができますが、ええ、それはあまりにも多く見えます。
追加された 著者 Grim Fandango,

はい、これはできますが、簡単にはできません。あなたが本当にコピーを避ける必要がない限り、私はそれが努力する価値があるとは思わない。

If you really want to do this, you can try creating your own iterator that iterates over the vector and a wrapper around Point.

The iterator class will dereference to an instance of the wrapper class. The wrapper class should support operator - or your distance function, and it should store the distance. You should then implement an operator for implicit conversion to double, which will be invoked when adjacent_difference attempts to assign the wrapper to the vector.

私は詳細に入る時間がないので、不明な点があれば、後でチェックするか、他の誰かが説明しやすくすることができます。以下はこれを行うラッパーの例です。

 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 values = {1, 2, 3, 5, 8}; 
 vector dist; 
 adjacent_difference(values.begin(), values.end(), back_inserter(dist), sub);

//dist = {1, 1, 1, 2, 3}
1
追加された
あなたの答えは明らかです。私はちょうどエレガントなソリューションを望んでいた。残念ながらSTLには何もない
追加された 著者 Grim Fandango,

問題を引き起こしている adjacent_difference によって返される最初の要素を使用していないので、最初の割り当てをスキップして独自のアルゴリズムを書くことができます:

template 
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
追加された
むしろ value_type の代わりにイテレータを使用したいと思います。それ以外の場合は、コピーが重いかサポートされていない場合でも問題になります。
追加された 著者 Christian Rau,
@ChristianRau:私はあなたが正しいと思います。私はあなたの提案に従ってコードを編集します。
追加された 著者 Gorpik,
@GrimFandango: adjacent_difference partial_sum の逆の操作です。おそらく、この第2の操作の名称が明白であろう。もちろん、STLのすべてのアルゴリズムと同様に、さまざまなことを行うために一般化することができます。
追加された 著者 Gorpik,
@GrimFandango: adjacent_difference の考え方は、累積に基づいて部分的な結果を提供することです。この場合、第1の区間の部分的な結果は蓄積されたものと同じである。柔軟性についてあなたが言うことは本当ですが。アルゴリズムは、最初の差を計算するためのオプションの初期値を要求している可能性があります。
追加された 著者 Gorpik,
@Gorpik:尋ねてくれてありがとうございますが、どこで " adjacent_difference のアイデアは蓄積された情報に基づいて部分的な結果を提供するのですか? ?いいえ、私はそれに同意すると確信していません。
追加された 著者 Grim Fandango,
私はなぜ adjacent_difference()のような無意味な実装を選んだのか分かりません。返されるこの最初の要素は、それほど柔軟性に欠けます。
追加された 著者 Grim Fandango,

私はa)問題の定式化、b)実行時間の比較、c)my_adjacent_difference、d)my_adjacent_differenceが組み込みの最適化を欠くかもしれないという自己コメントが好きです。私は、標準C ++のadjacent_differenceロジックがアルゴリズムのアプリケーションを制限し、3つのラインのループコードが多くの人にとっては必要な解決策であることに同意します。私は、アルゴリズム変換を適用し、ラムダを示すC ++ 11でバージョンを提示する考えを再利用します。よろしく。

#include              /* Standard C++ cout, cerr */
#include                /* Standard C++ vector */
#include             /* Standard C++ transform */
#include              /* Standard C++ back_inserter */
#include                 /* Standard C++ sqrt */
#include             /* 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  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(cout, "\n"));
    }
    catch(const exception& e) {
        cerr    << e.what() << endl;
        return  -1;
    }
    catch(...) {
        cerr    << "Unknown exception" << endl;
        return  -2;
    }
    return  0;
}

出力:

1
3
0
追加された