web-dev-qa-db-ja.com

for_eachを使用してベクトル内の値のインデックスを取得するにはどうすればよいですか?

私は次のコードを持っています(コンパイラ:MSVC++ 10):

std::vector<float> data;
data.Push_back(1.0f);
data.Push_back(1.0f);
data.Push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [](int value) {
     // Can I get here index of the value too?
});

上記のコードスニペットで必要なのは、ラムダ式内のデータベクトルの値のインデックスを取得することです。 for_eachは単一のパラメーター関数のみを受け入れるようです。 for_eachとlambdaを使用してこれに代わるものはありますか?

27
Liton

インデックスをキャプチャできるとは思いませんが、外部変数を使用してインデックスを作成し、ラムダにキャプチャすることができます。

int j = 0;
std::for_each(data.begin(), data.end(), [&j](float const& value) {
            j++;
});
std::cout << j << std::endl;

これにより、予想どおり3が出力され、jはインデックスの値を保持します。

実際のイテレータが必要な場合は、同様に行うことができます。

std::vector<float>::const_iterator it = data.begin();
std::for_each(data.begin(), data.end(), [&it](float const& value) {
            // here "it" has the iterator
            ++it; 
});
26
Diego Sevilla

このようなもの:

template <typename IteratorT, typename FunctionT>
FunctionT enumerate(IteratorT first, 
                    IteratorT last, 
                    typename std::iterator_traits<IteratorT>::difference_type initial,
                    FunctionT func)
{
    for (;first != last; ++first, ++initial)
        func(initial, *first);
    return func;
}

使用されます:

enumerate(data.begin(), data.end(), 0, [](unsigned index, float val)
{
    std::cout << index << " " << val << std::endl;
});
16
James McNellis

C++ 14では 一般化されたラムダキャプチャ のおかげで次のようなことができます:

std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [idx = 0] (int i) mutable {
    // your code...
    ++idx; // 0, 1, 2... 9
});
15
Tom

最も簡単な方法はstd::accumulateを使用することだと思います。

std::accumulate(data.begin(), data.end(), 0, [](int index, float const& value)->int{
    ...
    return index + 1;
});

このソリューションはanyコンテナーで機能し、変数またはカスタムクラスを必要としません。

10
vasalvit

または、&value-&data [0]を使用することもできますが、少し高価になる可能性があります。

std::for_each(data.begin(), data.end(), [&data](float const& value) {
    int idx = &value - &data[0];
});
9
James Harper

列挙するためにイテレータをラップする への別の方法:

必要なヘッダー:

#include <algorithm>
#include <iterator>
#include <utility>

ラッピングイテレータ:

template<class Iter, class Offset=int>
struct EnumerateIterator : std::iterator<std::input_iterator_tag, void, void, void, void> {
  Iter base;
  Offset n;

  EnumerateIterator(Iter base, Offset n = Offset()) : base (base), n (n) {}

  EnumerateIterator& operator++() { ++base; ++n; return *this; }
  EnumerateIterator operator++(int) { auto copy = *this; ++*this; return copy; }

  friend bool operator==(EnumerateIterator const& a, EnumerateIterator const& b) {
    return a.base == b.base;
  }
  friend bool operator!=(EnumerateIterator const& a, EnumerateIterator const& b) {
    return !(a == b);
  }

  struct Pair {
    Offset first;
    typename std::iterator_traits<Iter>::reference second;

    Pair(Offset n, Iter iter) : first (n), second(*iter) {}

    Pair* operator->() { return this; }
  };

  Pair operator*() { return Pair(n, base); }
  Pair operator->() { return Pair(n, base); }
};

オーバーロードを列挙します。

template<class Iter, class Func>
Func enumerate(Iter begin, Iter end, Func func) {
  typedef EnumerateIterator<Iter> EI;
  return std::for_each(EI(begin), EI(end), func);
}
template<class T, int N, class Func>
Func enumerate(T (&a)[N], Func func) {
  return enumerate(a, a + N, func);
}
template<class C, class Func>
Func enumerate(C& c, Func func) {
  using std::begin;
  using std::end;
  return enumerate(begin(c), end(c), func);
}

ジェームズからコピーされたテスト:

#include <array>
#include <iostream>

struct print_pair {
  template<class Pair>
  void operator()(Pair const& p) {
    std::cout << p.first << ": " << p.second << "\n";
  }
};

int main() {
  std::array<float, 5> data = {1, 3, 5, 7, 9};
  enumerate(data, print_pair());
  return 0;
}

ここにはオフセットの提供は含まれていません。ただし、EnumerateIteratorでは0以外で開始する準備ができています。残りの選択肢は、オフセットを作成するタイプと、追加パラメーターにオーバーロードを追加するか、デフォルト値を使用するかです。 (オフセットがイテレータの差分タイプである必要がある理由はありません。たとえば、各反復が翌日に対応する、日付関連のタイプにした場合はどうなりますか?)

2
Roger Pate

Roger Pateは、他の回答へのコメントで、列挙を実行するイテレーターラッパーを作成することを提案しました。それを実装することは少し打撃でした。

このイテレーターラッパーは、値型がTのフォワードイテレーター(「内部イテレーター」と呼ばれます)を受け取り、値型がpair<int, T&>のフォワードイテレーターに変換します。ここでintは内部イテレータの距離タイプです。

これは、2つのことを除いて、非常に簡単です。

  • std::pairコンストラクターはconst参照によって引数を受け取るため、タイプT&のデータメンバーを初期化することはできません。イテレータ用に独自のペアタイプを作成する必要があります。
  • イテレータの正しいセマンティクスをサポートするには、左辺値が必要です(operator*は参照を返す必要があり、operator->はポインタを返す必要があります)。したがって、ペアはのデータメンバーである必要があります。イテレータ。参照が含まれているため、参照を「リセット」する方法が必要です。また、エンドイテレータを正しく処理できるように、遅延初期化する必要があります。 Tが割り当てられない場合、boost::optional<T>は気に入らないようです。そのため、独自の単純なlazy<T>を作成します。

lazy<T>ラッパー:

#include <new>
#include <type_traits>

// A trivial lazily-initialized object wrapper; does not support references
template<typename T>
class lazy
{
public:

    lazy() : initialized_(false) { }
    lazy(const T& x) : initialized_(false) { construct(x); }

    lazy(const lazy& other)
        : initialized_(false)
    {
        if (other.initialized_)
            construct(other.get());
    }

    lazy& operator=(const lazy& other)
    {
        // To the best of my knowledge, there is no clean way around the self
        // assignment check here since T may not be assignable
        if (this != &other)
            construct(other.get());
        return *this;
    }

    ~lazy() { destroy(); }

    void reset() { destroy(); }
    void reset(const T& x) { construct(x); }

          T& get()       { return reinterpret_cast<      T&>(object_); }
    const T& get() const { return reinterpret_cast<const T&>(object_); }

private:

    // Ensure lazy<T> is not instantiated with T as a reference type
    typedef typename std::enable_if<
        !std::is_reference<T>::value
    >::type ensure_t_is_not_a_reference;

    void construct(const T& x) 
    {
        destroy();
        new (&object_) T(x); 
        initialized_ = true;
    }

    void destroy() 
    { 
        if (initialized_)
            reinterpret_cast<T&>(object_).~T();
        initialized_ = false;
    }

    typedef typename std::aligned_storage<
        sizeof T, 
        std::alignment_of<T>::value
    >::type storage_type;

    storage_type object_;
    bool initialized_;
};

enumerating_iterator

#include <iterator>
#include <type_traits>

// An enumerating iterator that transforms an iterator with a value type of T
// into an iterator with a value type of pair<index, T&>.
template <typename IteratorT>
class enumerating_iterator
{
public:

    typedef IteratorT                              inner_iterator;
    typedef std::iterator_traits<IteratorT>        inner_traits;
    typedef typename inner_traits::difference_type inner_difference_type;
    typedef typename inner_traits::reference       inner_reference;

    // A stripped-down version of std::pair to serve as a value type since
    // std::pair does not like having a reference type as a member.
    struct value_type
    {
        value_type(inner_difference_type f, inner_reference s)
            : first(f), second(s) { }

        inner_difference_type first;
        inner_reference       second;
    };

    typedef std::forward_iterator_tag iterator_category;
    typedef inner_difference_type     difference_type;
    typedef value_type&               reference;
    typedef value_type*               pointer;

    explicit enumerating_iterator(inner_iterator it = inner_iterator(), 
                                  difference_type index = 0) 
        : it_(it), index_(index) { }

    enumerating_iterator& operator++() 
    {
        ++index_;
        ++it_;
        return *this;
    }

    enumerating_iterator operator++(int)
    {
        enumerating_iterator old_this(*this);
        ++*this;
        return old_this;
    }

    const value_type& operator*() const 
    { 
        value_.reset(value_type(index_, *it_));
        return value_.get();
    }

    const value_type* operator->() const { return &**this; }

    friend bool operator==(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return lhs.it_ == rhs.it_;
    }

    friend bool operator!=(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return !(lhs == rhs);
    }

private:

    // Ensure that the template argument passed to IteratorT is a forward
    // iterator; if template instantiation fails on this line, IteratorT is
    // not a valid forward iterator:
    typedef typename std::enable_if<
        std::is_base_of<
            std::forward_iterator_tag,
            typename std::iterator_traits<IteratorT>::iterator_category
        >::value
    >::type ensure_iterator_t_is_a_forward_iterator;

    inner_iterator it_;              //< The current iterator
    difference_type index_;          //< The index at the current iterator
    mutable lazy<value_type> value_; //< Pair to return from op* and op->
};

// enumerating_iterator<T> construction type deduction helpers
template <typename IteratorT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it)
{
    return enumerating_iterator<IteratorT>(it);
}

template <typename IteratorT, typename DifferenceT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it, DifferenceT idx)
{
    return enumerating_iterator<IteratorT>(it, idx);
}

テストスタブ:

#include <algorithm>
#include <array>
#include <iostream>

struct print_pair
{
    template <typename PairT> 
    void operator()(const PairT& p)
    {
        std::cout << p.first << ": " << p.second << std::endl;
    }
};

int main()
{
    std::array<float, 5> data = { 1, 3, 5, 7, 9 };

    std::for_each(make_enumerator(data.begin()), 
                  make_enumerator(data.end()), 
                  print_pair());
}

これは最小限のテスト済みです。 Comeauとg ++ 4.1はどちらも、C++ 0xタイプの特性とaligned_storageを削除すれば、それを受け入れます(このラップトップには、テストする新しいバージョンのg ++​​がありません)。バグを見つけたら教えてください。

これを改善する方法についての提案に非常に興味があります。具体的には、Boostの何かを使用するか、イテレータ自体を変更することによって、lazy<T>を使用しなければならない方法があるかどうかを知りたいです。私はただ馬鹿げているだけで、これをもっときれいに実装する本当に簡単な方法があることを願っています。

2
James McNellis

CおよびC++の標準規則に従い、最初の要素のインデックスは0で、最後の要素のインデックスはsize()-1です。

したがって、次のことを行う必要があります;-

std::vector<float> data;
int index = 0;

data.Push_back(1.0f);
data.Push_back(1.0f);
data.Push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [&index](float value) {
// Can I get here index of the value too?
   cout<<"Current Index :"<<index++; // gets the current index before increment
});
1
yadab

ラムダ関数で、値intの代わりにint&を渡すと、アドレスが得られます。 &それを使用して、最初のアイテムから自分の位置を推測することができます

それはうまくいくでしょうか? for_eachが参照をサポートしているかどうかわかりません

0
ianmac45

Std :: for_eachに3番目の引数として構造体を渡し、その中のインデックスを次のようにカウントすることもできます。

struct myStruct {
   myStruct(void) : index(0) {};
   void operator() (float i) { cout << index << ": " << i << endl; index++; }
   int index;
};

int main()
{

   std::vector data;
   data.Push_back(1.0f);
   data.Push_back(4.0f);
   data.Push_back(8.0f);

   // lambda expression
   std::for_each(data.begin(), data.end(), myStruct());

   return 0;
}
0
Elrohir