【问题标题】:How to obtain index of element from predicate passed to some STL algorithm?如何从传递给某些 STL 算法的谓词中获取元素的索引?
【发布时间】:2012-02-28 11:09:11
【问题描述】:

说,我有元素向量和掩码数组,我想从向量中提取具有真正对应掩码值的元素以分离向量。有没有办法为此目的使用std::copy_if?问题是,我只有 value 在谓词中的元素,而不是 iterator,所以我不知道地址掩码数组的实际索引。

我可以像这样直接操作地址:

vector<bool> mask;
vector<int> a, b;
copy_if(a.begin(), a.end(), b.begin(), [&] (int x) -> bool { 
  size_t index = &x - &a[0]; // Ugly...
  return mask[index];
});

但是,我发现这是一个丑陋的解决方案。有更好的想法吗?

更新:另一种可能的解决方案是在掩码上使用外部迭代器:

vector<bool> mask;
vector<int> a, b;
auto pMask = mask.begin();
copy_if(a.begin(), a.end(), b.begin(), [&] (int x) { 
  return *pMask++;
});

但是,此解决方案需要外部命名空间中的其他变量,这仍然是不可取的。

【问题讨论】:

    标签: c++ algorithm stl iterator predicate


    【解决方案1】:

    好的,经过一番调查,我提出了第一个示例,这是最简单的方法。但是,不要忘记通过 (const) 引用在 lambda 中传递值,以免获取参数的本地副本的地址:

    copy_if(a.begin(), a.end(), b.begin(), 
      [&] (const int& x) -> bool {  // <-- do not forget reference here
        size_t index = &x - &a[0];  // Still ugly... but simple
        return mask[index];
      });
    

    【讨论】:

      【解决方案2】:

      您可以将多个迭代器组合成 Boost(未经真正测试,但可以使用 GCC 4.6 编译):

      #include <algorithm>
      
      #include <boost/iterator/counting_iterator.hpp>
      #include <boost/iterator/zip_iterator.hpp>
      #include <boost/iterator/filter_iterator.hpp>
      #include <boost/tuple/tuple.hpp>
      
      int main() {
        std::vector<bool> mask;
        std::vector<int> a, b;
        boost::counting_iterator<size_t> count_begin(0), count_end(a.size());
        auto zip_begin = boost::make_zip_iterator(boost::make_tuple(count_begin, a.begin()));
        auto zip_end = boost::make_zip_iterator(boost::make_tuple(count_end, a.end()));
        typedef decltype(zip_end) zip_iterator;
        typedef const zip_iterator::value_type& zip_value;
        auto pred = [&mask](zip_value val) {
          auto index = val.get<0>();
          return index < mask.size() ? mask[index] : true;
        };
        auto filter_begin = boost::make_filter_iterator(pred, zip_begin, zip_end);
        auto filter_end = boost::make_filter_iterator(pred, zip_end, zip_end);
        std::transform(filter_begin, filter_end, back_inserter(b), [](zip_value val) {
            return val.get<1>();
          });
      }
      

      但是,我认为显式循环在这里更简单。


      这是上面代码的另一个更通用的版本,这次甚至测试了:) 它提供了类似 Python 的 mapfilterenumerate 函数的实现。这个需要 GCC 4.7。

      #include <utility>
      #include <vector>
      #include <iterator>
      #include <type_traits>
      #include <iostream>
      
      #define BOOST_RESULT_OF_USE_DECLTYPE
      
      #include <boost/tuple/tuple.hpp>
      #include <boost/iterator/zip_iterator.hpp>
      #include <boost/iterator/filter_iterator.hpp>
      #include <boost/iterator/transform_iterator.hpp>
      #include <boost/range/begin.hpp>
      #include <boost/range/end.hpp>
      #include <boost/range/size.hpp>
      #include <boost/range/iterator_range.hpp>
      #include <boost/range/counting_range.hpp>
      #include <boost/range/algorithm/copy.hpp>
      #include <boost/range/algorithm_ext/push_back.hpp>
      
      template<typename... ForwardRange>
      using zip_range = boost::iterator_range<
        boost::zip_iterator<
          boost::tuple<
            typename boost::range_iterator<
              typename std::remove_reference<ForwardRange>::type>::type...>>>;
      
      template<typename... ForwardRange>
      zip_range<ForwardRange...>
      zip(ForwardRange&&... ranges) {
        return boost::make_iterator_range(
          boost::make_zip_iterator(
            boost::make_tuple(
              boost::begin(std::forward<ForwardRange>(ranges))...)),
          boost::make_zip_iterator(
            boost::make_tuple(
              boost::end(std::forward<ForwardRange>(ranges))...)));
      }
      
      template<typename ForwardRange, typename Index>
      using enumerating_range = zip_range<
        boost::iterator_range<boost::counting_iterator<Index>>,
        ForwardRange>;
      
      template<typename ForwardRange, typename Index>
      enumerating_range<ForwardRange, Index>
      enumerate(ForwardRange&& range, Index start) {
        return zip(
          boost::counting_range(
            start,
            static_cast<Index>(start + boost::size(range))),
          std::forward<ForwardRange>(range));
      }
      
      template<typename Predicate, typename ForwardRange>
      using filter_range = boost::iterator_range<
        boost::filter_iterator<
          Predicate,
          typename boost::range_iterator<
            typename std::remove_reference<ForwardRange>::type>::type>>;
      
      template<typename Predicate, typename ForwardRange>
      filter_range<Predicate, ForwardRange>
      filter(Predicate pred, ForwardRange&& range) {
        return boost::make_iterator_range(
          boost::make_filter_iterator(
            pred,
            boost::begin(std::forward<ForwardRange>(range))),
          boost::make_filter_iterator(
            pred,
            boost::end(std::forward<ForwardRange>(range))));
      }
      
      template<typename UnaryOperation, typename ForwardRange>
      using map_range = boost::iterator_range<
        boost::transform_iterator<
          UnaryOperation,
          typename boost::range_iterator<
            typename std::remove_reference<ForwardRange>::type>::type>>;
      
      template<typename UnaryOperation, typename ForwardRange>
      map_range<UnaryOperation, ForwardRange>
      map(UnaryOperation operation, ForwardRange&& range) {
        return boost::make_iterator_range(
          boost::make_transform_iterator(
            boost::begin(std::forward<ForwardRange>(range)),
            operation),
          boost::make_transform_iterator(
            boost::end(std::forward<ForwardRange>(range)),
            operation));
      }
      
      template<typename UnaryOperation, typename Predicate, typename ForwardRange>
      using filter_map_range = map_range<
        UnaryOperation,
        filter_range<Predicate, ForwardRange>>;
      
      template<typename UnaryOperation, typename Predicate, typename ForwardRange>
      filter_map_range<UnaryOperation, Predicate, ForwardRange>
      filter_map(UnaryOperation operation, Predicate pred, ForwardRange&& range) {
        return map(operation, filter(pred, range));
      }
      
      int main() {
        std::vector<int> a { 10, 11, 12, 13, 14 };
        std::vector<bool> mask { false, true, true, false, true };
        std::vector<int> b;
        auto enumerator = enumerate(a, 0u);
        typedef boost::range_value<decltype(enumerator)>::type enum_value;
        boost::push_back(
          b,
          filter_map(
            [](const enum_value& val) {
              return val.get<1>();
            },
            [&mask](const enum_value& val) {
              auto i = val.get<0>();
              return i < mask.size() ? mask[i] : true;
            },
            enumerator));
        boost::copy(b, std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;
      }
      

      如果你不需要使用向量,解决方案会变得有些无聊:

      #include <valarray>
      #include <algorithm>
      #include <iterator>
      #include <iostream>
      
      int main() {
        using namespace std;
        valarray<int> a { 10, 11, 12, 13, 14 };
        valarray<bool> mask { false, true, true, false, true };
        valarray<int> b = a[mask];
        copy(begin(b), end(b), ostream_iterator<int>(cout, " "));
      }
      

      【讨论】:

        【解决方案3】:

        我的回答:

            vector<bool> mask ;
            vector<int> a, b;
            auto it = std::copy_if (a.begin(), a.end(), b.begin(), [&, index = 0] (const int x) mutable -> bool { 
            return mask[index++]; // increment index
            });
        

        这使用了一个状态完整的 lambda。 index 仅设置为零一次,每次使用时都会递增。 编辑: 需要 c++14

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-10
          • 2020-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多