如何从谓词中获取元素索引传递给某些STL算法?

时间:2012-02-28 11:09:11

标签: c++ algorithm stl iterator predicate

说,我有元素矢量和掩码数组,我想从矢量中提取具有真实对应掩码值的元素来分离矢量。有没有办法将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++;
});

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

3 个答案:

答案 0 :(得分:5)

好的,经过一番调查后我发现第一个例子是最简单的方法。但是,不应忘记通过(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];
  });

答案 1 :(得分: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, " "));
}

答案 2 :(得分:2)

我的答案:

    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