【问题标题】:How to get the index of a value in a vector using for_each?如何使用 for_each 获取向量中值的索引?
【发布时间】:2011-04-14 16:48:25
【问题描述】:

我有以下代码(编译器: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?
});

我在上面的代码sn-p中想要的是获取lambda表达式内部数据向量中的值的索引。似乎 for_each 只接受一个参数函数。有没有使用 for_each 和 lambda 的替代方法?

【问题讨论】:

    标签: c++ c++11 lambda


    【解决方案1】:

    感谢generalized lambda captures,在 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
    });
    

    【讨论】:

      【解决方案2】:

      我不认为您可以捕获索引,但您可以使用外部变量进行索引,将其捕获到 lambda 中:

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

      【讨论】:

      • 第二个代码仅适用于前向迭代器(即与输入迭代器中断;这可能是也可能不是问题,但仍然必须知道)。一般来说,两者都存在外部存储状态的问题,如果没有它会更好。
      • @Roger:是的,关于输入迭代器,你是完全正确的......他/她只是想在数组中使用它。我想更好的全能方式是将for_each 重写为其他响应中的enumerate
      【解决方案3】:

      类似这样的:

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

      【讨论】:

      • 没关系,但如果我采用它,我会称它为for_each_indexed,以保持for_each 作为名称的开头。
      • 我称它为枚举并将 initial 作为最后一个参数,默认值为 0。下一个选择是包含计数的迭代器包装器(例如 T 的迭代器变为pair) 的迭代器;不幸的是,使用 STL 风格的迭代器要复杂得多。
      • @Roger:我喜欢称它为enumerate 的想法。我将初始索引放置在结束迭代器和函数之间,以便它与accumulate 参数排序有些匹配。我认为必须将初始值放在 lambda 之后可能会很尴尬。添加另一个只接受三个参数并自行从零开始的重载可能会更简洁。至于迭代器,这将是一个打击,因为您确实希望值类型为pair&lt;int, T&amp;&gt;,因此您可以引用基础元素。这绝对是可能的,只是乏味。
      • @Roger:我为它实现了一个迭代器包装器。请参阅下面的答案;如果您有任何建议,请告诉我......我有一种烦人的感觉,我错过了一些非常非常简单的东西,这将大大简化实施。我希望这里的一位聪明人能告诉我什么。 :-)
      • 是的,我的意思不是字面上的 T,只是想用标准类型来表达观点,而不是写一个完整的答案。 (理想情况下,您会检查 lambda 参数并转发。)即使稍后放置初始索引值(哦,我希望命名参数如何)有时可能会很尴尬,但我宁愿不要使重载和通常情况复杂化可能是使用默认值。
      【解决方案4】:

      或者,您可以使用 &value - &data[0],尽管它可能会贵一些。

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

      【讨论】:

      • 来吧....这是一个不错的方法,难道不能有人对此表示赞同并让我获得 2 个声誉吗?
      • @sebleblanc OP 表示“lambda 表达式内数据向量中的值的索引”。只要它适用于矢量,我就不明白为什么我的方法无效并且不值得赞成。我敢肯定它比其他方法更漂亮!
      • 请注意,如果您尝试在 C++17 中使用并行的 for_each,那么其他几个答案就会分崩离析,而这也应该在那里工作。
      【解决方案5】:

      我认为最简单的方法是使用std::accumulate

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

      此解决方案适用于 任何 容器,不需要变量或自定义类。

      【讨论】:

        【解决方案6】:

        Roger Pate 在对我的其他答案的评论中建议创建一个执行枚举的迭代器包装器。实施起来有点麻烦。

        这个迭代器包装器接受一个值类型为T的前向迭代器(称为“内部迭代器”)并将其转换为一个值类型为pair&lt;int, T&amp;&gt;的前向迭代器,其中int是距离类型内部迭代器。

        这很简单,除了两件事:

        • std::pair 构造函数通过 const 引用获取其参数,因此我们无法初始化 T&amp; 类型的数据成员;我们必须为迭代器创建自己的对类型。
        • 为了支持迭代器的正确语义,我们需要一个左值(operator*需要返回一个引用,operator-&gt;需要返回一个指针),所以pair需要是迭代器的数据成员.由于它包含一个引用,我们需要一种“重置”它的方法,并且我们需要延迟初始化它,以便我们可以正确处理结束迭代器。如果T 不可赋值,boost::optional&lt;T&gt; 似乎不喜欢它,所以我们将编写自己的简单lazy&lt;T&gt;

        lazy&lt;T&gt; 包装器:

        #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());
        }
        

        这已经过最低限度的测试;如果我删除 C++0x 类型特征和 aligned_storage,Comeau 和 g++ 4.1 都接受它(我在这台笔记本电脑上没有更新版本的 g++ 来测试)。如果您发现任何错误,请告诉我。

        我对如何改进这一点的建议非常感兴趣。具体来说,我很想知道是否有办法绕过必须使用lazy&lt;T&gt;,要么使用Boost 中的某些东西,要么修改迭代器本身。我希望我只是愚蠢,实际上有一种非常简单的方法可以更干净地实现这一点。

        【讨论】:

        • “实施它有点吃力,”确实。 :)
        • template&lt;class Iter, class Func&gt; Func enumerate(Iter begin, Iter end, Func func) { return std::for_each(make_enumerator(begin), make_enumerator(end), std::forward&lt;Func&gt;(func)); } template&lt;class T, int N, class Func&gt; Func enumerate(T (&amp;a)[N], Func func) { return enumerate(std::begin(a), std::end(a), std::forward&lt;Func&gt;(func)); } template&lt;class C, class Func&gt; Func enumerate(C&amp; c, Func func) { using std::begin; using std::end; return enumerate(begin(c), end(c), std::forward&lt;Func&gt;(func)); }
        • (由于obscure corner case,您必须特别对待数组。)
        • My implementation 通过消除 lazy 以及所有的构造和破坏来更简单一些。 (其他差异应该是肤浅的。)
        【解决方案7】:

        wrap iterators for enumerate的另一种方式:

        必需的标题:

        #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);
        }
        

        从 James 那里复制的测试:

        #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 以外的地方开始。剩下的选择是要制作偏移的类型以及是否为额外参数添加重载或使用默认值。 (没有理由偏移量必须是迭代器的差异类型,例如,如果您将其设为与日期相关的类型,每次迭代对应于第二天呢?)

        【讨论】:

        • +10 如果我能指出一种为自然具有右值元素的迭代器实现 operator-&gt; 的好方法...
        • @James:特别是考虑到我上面采用的快捷方式(例如 std::iterator 的 voids),我不得不说我非常喜欢将两个参数传递给功能。 :(
        【解决方案8】:

        按照 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
        });
        

        【讨论】:

          【解决方案9】:

          也许在 lambda 函数中,将 int&amp; 传递给它而不是值 int,这样你就有了地址。 & 然后你可以用它从第一项推断你的位置

          这行得通吗?我不知道 for_each 是否支持引用

          【讨论】:

            【解决方案10】:

            您还可以将结构作为第三个参数传递给 std::for_each 并计算其中的索引,如下所示:

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

            【讨论】:

              猜你喜欢
              • 2020-03-26
              • 1970-01-01
              • 2020-08-04
              • 2010-09-13
              • 2022-06-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多