How to obtain index of element from predicate passed to some STL algorithm?

此生再无相见时 提交于 2019-12-21 09:14:03

问题


Say, I have vector of elements and a mask array, and I want to extract elements from vector with true corresponding mask value to separate vector. Is there a way to use std::copy_if for this purpose? The problem is, I only have value of element inside predicate, not iterator to it, so I cannot know the actual index to address mask array.

I can directly manipulate addresses like this:

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

However, I find this to be ugly solution. Any better ideas?

Update: Another possible solution is to use external iterator on mask:

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

However, this solution requires additional variable in outer namespace which still is not desirable.


回答1:


Ok, after a bit of investigation I come out with the first example be the easiest way. However, one should not forget to pass value in lambda by (const) reference for not to take address of local copy of a parameter:

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:


You could combine several iterators form Boost (not really tested, but compiles with 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>();
    });
}

However, I think an explicit loop is just simpler here.


Here is another more generalized version of the above code, this time even tested :) It provides implementations for Python-like map, filter and enumerate functions. This one requires 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;
}

If you aren't required to use vectors, the solution becomes somewhat boring:

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


来源:https://stackoverflow.com/questions/9480785/how-to-obtain-index-of-element-from-predicate-passed-to-some-stl-algorithm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!