std::transform による、データ加工

[戻る]
白州 2009/09/22(火) 15:00:19
宜しくお願いします、下記の条件を満たしたプログラムを作りたいのですが
上手くいきません、ご教示ねがいます。
※ 条件
・配列x , 配列y 共に昇順に並んでいる。
・配列x , 配列y の要素数は等しくない。
・x[i] とy[i] が等しければz[i] に代入。
・配列y の要素は、配列x 以外の要素を持たない。
・配列y の要素が、配列x に含まれていなければ、その位置に0 を代入。( z[i]=0 )
・他のコンテナでも同じ振る舞いになること。

※ 現在の動作
IN
int x[6] = { 1, 2, 3, 4, 5, 6 };
int y[4] = { 1, 3, 4, 6 };
OUT
int z[6] = {1, 0, 0, 0, 0, 0 };

※ 期待する動作
OUT
int z[6] = {1, 0, 3, 4, 0, 6 };

#include <iostream>
#include <algorithm>

template <class T, class N>class IsEqual {
public:
    T operator()(T t, N n){
        return t == n ? t : 0;
    }
};

int main() {
    int x[6] = { 1, 2, 3, 4, 5, 6 };
    int y[4] = { 1, 3, 4, 6 };
    int z[6];
    std::transform(    x, x+6, y, z, IsEqual<int, int>());
    for ( int i = 0; i < 6; ++i ) {
        std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    }
    return 0;
}

Blue 2009/09/22(火) 15:34:00
>・x[i] とy[i] が等しければz[i] に代入。>int z[6] = {1, 0, 3, 4, 0, 6 };
は違う気がするけど。

x[i] と y[j] が等しければ z[i] に x[i] を代入、等しくなければ 0 を代入ってこと?


std::transform の5つの引数のやつは、3番目に渡すものの配列サイズが渡せないとお
もうので、関数オブジェクトのメンバとして持たせるのはどうでしょうか?

たとえば適当に

#include <iostream>
#include <algorithm>
#include <vector>

template <class T, class N>class IsEqual {
    T* p_;
    size_t s_;
//  std::vector<T>* pv_;
public:
    IsEqual(T* p, size_t s) : p_(p), s_(s) {}
    T operator()(T t)
    {
        for (size_t i = 0; i < s_; ++i)
        {
            if (p_[i] == t) {
                return t;
            }
        }
        return 0;
    }
//  IsEqual(std::vector<T>* pv) : pv_(pv) {}
//  T operator()(T t) {
//      return  (std::find(pv_->begin(), pv_->end(), t) != pv_->end()) ? t : 0;
//  }
};

int main() {
    int x[6] = { 1, 2, 3, 4, 5, 6 };
    int y[4] = { 1, 3, 4, 6 };
//  std::vector<int> y;
//  y.push_back(1);
//  y.push_back(3);
//  y.push_back(4);
//  y.push_back(6);
    int z[6];
    std::transform(    x, x+6, z, IsEqual<int, int>(y, 4));
//  std::transform(    x, x+6, z, IsEqual<int, int>(&y));
    for ( int i = 0; i < 6; ++i ) {
        std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    }
    return 0;
}

とか。

Blue 2009/09/22(火) 15:37:27
↑
は IsEqual の class N 全く使ってなかった。

Blue 2009/09/22(火) 15:43:20
IsEqual::operator()の
>        return 0;
はTが数値型のときしかだめですね。

return T();

でいけたかしら。

白州 2009/09/22(火) 19:24:56
Blue さん、ありがとうございます、期待していた動作です。
ところで、コンテナがvector である場合、template の部分特殊化。
または、オーバーロードによりvector コンテナのときに限った、動作を定義させること
はできないものでしょうか?

白州 2009/09/22(火) 19:48:59
Blue さんの質問に答えていませんでした失礼しました
>x[i] と y[j] が等しければ z[i] に x[i] を代入、等しくなければ 0 を代入ってこと?
そういうことです。表現が変でしたすみません。それから
部分特殊化を調べたり、勉強しながら考えているのですが、上手くいきません?
#include <iostream>
#include <algorithm>
#include <vector>

template <class T>class IsEqual {
    T* p_;
    size_t s_;
public:
    IsEqual(T* p, size_t s) : p_(p), s_(s) {}
    T operator()(T t)
    {
      for (size_t i = 0; i < s_; ++i)
      {
          if (p_[i] == t) {
              return t;
          }
      }
      return T();
    }
};

template <class T>class IsEqual<T *> {
    std::vector<T>* pv_;
public:
    IsEqual(std::vector<T>* pv) : pv_(pv) {}
    T operator()(T t) {
        return  (std::find(pv_->begin(), pv_->end(), t) != pv_->end()) ? t : 0;
    }
};

int main() {
    typedef std::vector<int> VEC;
    int x[6] = { 1, 2, 3, 4, 5, 6 };
    int z[6];
    //int y[4] = { 1, 3, 4, 6 };
    //std::transform(x, x+6, z, IsEqual<int>(y, 4));
    //for ( int i = 0; i < 6; ++i ) {
    //  std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    //}

    VEC y;
    y.push_back(1);
    y.push_back(3);
    y.push_back(4);
    y.push_back(6);
    VEC::iterator iter = y.begin();
    int len = sizeof(x)/sizeof(int);
    std::transform( x, x+len, z, IsEqual<int>(iter));    // ここの書き方が分かりません?
    for ( int i = 0; i < 6; ++i ) {
        std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    }
    return 0;
}

白州 2009/09/23(水) 00:43:54
なんとか、std:vector<int> 型をtemplate 部分特殊化することが出来ました。
掲示はしませんが、int 型へ特殊化することも成功しました、しかし思うに、
更に汎用的に扱いたければ、iterator 、ネィティブな配列でのアクセスできれば、より
多くの関数を効率よく生成することが出来そうですね。
もう少しEffective な方法を思案してみます。

#include <iostream>
#include <algorithm>
#include <vector>

template <class T>class IsEqual {
    T* p_;
    size_t s_;
public:
    IsEqual(T* p, size_t s) : p_(p), s_(s) {}
    T operator()(T t)
    {
      for (size_t i = 0; i < s_; ++i)
      {
          if (p_[i] == t) {
              return t;
          }
      }
      return T();
    }
};

template <>class IsEqual<int> {
    std::vector<int>* pv_;
public:
    IsEqual(std::vector<int>* pv) : pv_(pv) {}
    int operator()(int t) {
        return  (std::find(pv_->begin(), pv_->end(), t) != pv_->end()) ? t : 0;
    }
};

int main() {

    int x[6] = { 1, 2, 3, 4, 5, 6 };
    int z[6];
    //int y[4] = { 1, 3, 4, 6 };
    //std::transform(x, x+6, z, IsEqual<int>(y, 4));
    //for ( int i = 0; i < 6; ++i ) {
    //  std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    //}

    std::vector<int> y;
    y.push_back(1);
    y.push_back(3);
    y.push_back(4);
    y.push_back(6);
    int len = sizeof(x)/sizeof(int);
    std::transform( x, x+len, z, IsEqual<int>(&y));
    for ( int i = 0; i < 6; ++i ) {
        std::cout << "z[" << x[i] << "] = " << z[i] << std::endl;
    }
    return 0;
}

Blue 2009/09/23(水) 08:58:20
こんな感じにしたらどうでしょうか?

template<class _InIt, class _Ty>
class IsEqual {
    _InIt First;
    _InIt Last;
public:
    IsEqual(_InIt _First, _InIt _Last) : First(_First), Last(_Last) {}
    _Ty operator()(_Ty _Val) {
         return  (std::find(First, Last, _Val) != Last) ? _Val : _Ty();
    }
};

// vector
std::transform(x, x + 6, z, IsEqual<std::vector<int>::iterator, int>(y.begin(),
y.end()));

// 配列
std::transform(x, x + 6, z, IsEqual<int*, int>(y, y + 4));

白州 2009/09/23(水) 19:37:25
[[解決]]
Blue さん、お世話になります。
iterator をpointer に置き換えるテクニックですね、ご教授ありがとうございます。
さて、「C++テンプレートテクニック」という本を最近購読し、この本を勉強しているの
ですが
Blue さんは、ご存知かと思いますが、この本の中に「SFINAE」Substitution Failure Is
Not An Error
という技術が紹介されています、直訳すると「置き換え失敗はエラーじゃない」と
大変格好悪い訳になりますが、この技術に魅せられてしまいました
運よくtransform の実装が掲載されていたので、そこでこのIsEqual を組み込もうとしたら、
引数の数を勘違いしており子2時間、悶々としていましたが、今さっき間違いに気付き、
修正し上手く組み込めました。
大変ありがとうございました、すっきりしました。

    // transform
    template <class Range, class InputIterator, class OutputIterator, class
BinaryOperation>
    OutputIterator transform
    (
        const Range& r,
        InputIterator first,
        OutputIterator result,
        BinaryOperation op
    )
    {
        return std::transform(begin(r), end(r), first, result, op);
    }

※ begin(r), end(r)はネイティブなC言語配列でも、イテレータと同じように実装できる
(凄い!)

επιστημη [E-Mail] [HomePage] 2009/09/25(金) 09:46:08
# 今日はゴミの日^^;
> 「C++テンプレートテクニック」

なんか地味に売れてますねぇ。ありがたやありがたや。

επιστημη [E-Mail] [HomePage] 2009/09/25(金) 11:31:43
ゴミだけじゃァレなので lambda 使ってみたよ♪

// Visual Studio 2010β で検証済
#include <iostream>
#include <algorithm>

int main() {
  int x[6] = { 1, 2, 3, 4, 5, 6 };
  int y[4] = { 1, 3, 4, 6 };
  int z[6];

  std::transform(x, x+6, z,
    [&](int v) { return std::find(y,y+4,v) == y+4 ? 0 : v; }); // あほほど楽チン
  std::for_each(z, z+6, [](int v) { std::cout << v << ' ';});
}

古いスレッドには追加発言できません。