使用条件迭代器插入向量

时间:2018-07-30 13:33:51

标签: c++ iterator

说我有一个带有各种条目的vector,我想将其插入另一个向量中,而忽略满足条件的条目。

例如,我想插入一个向量,而忽略所有三个。

{1, 3, 2, 3, 4, 5, 3} -> { /* previous content, */ 1, 2, 4, 5}

到目前为止,我想出的是使用std::partition,它不会保留相对顺序并重新排列源向量。

std::vector<int> source({1, 3, 2, 3, 4, 5, 3});
std::vector<int> target;

auto partition = std::partition(std::begin(source),
                   std::end(source), [](const auto& a) { return a == 3; });
target.insert(std::begin(target), partition, std::end(source));

我要寻找的更多是一种迭代器,它检查条件并在条件不满足时继续进行。像这样:

target.insert(std::begin(target),
              conditional_begin(source, [](const auto& a) { return a != 3; },
              conditional_end(source));

我认为有必要使用conditional_end函数,因为std::end返回的迭代器类型将不同于conditional_begin

也许我忽略了一些东西,所以我的问题是:

  • 标准库是否提供类似的内容?
  • 有其他简单的方法可以实现我的目标吗?
  • 是否有一种简单的方法来实现条件迭代器功能?

3 个答案:

答案 0 :(得分:11)

  

有其他简单的方法可以实现我的目标吗?

是的,标准已经内置了此功能。您要寻找的功能是std::copy_if

std::vector<int> source({1, 3, 2, 3, 4, 5, 3});
std::vector<int> target;

std::copy_if(source.begin(), 
             source.end(), 
             std::back_inserter(target), [](auto val){ return val != 3; });

此处std::back_inserter(target)将对谓词返回push_back的每个元素在target上调用true

答案 1 :(得分:3)

是的,您可以创建一个满足您需要的自定义迭代器,但是使用标准C ++创建自定义迭代器目前有点繁琐。看起来像这样:

template <typename Itr, typename F> 
struct ConditionalIterator {
  Itr itr;
  Itr end;
  F condition;

  using value_type = typename Itr::value_type;
  using difference_type = typename Itr::difference_type;
  using pointer = typename Itr::pointer;
  using reference = typename Itr::reference;
  using iterator_category = std::forward_iterator_tag;

  ConditionalIterator() = default;
  ConditionalIterator(Itr itr, Itr end, F condition): itr(itr), end(end), condition(condition) {}

  bool      operator!=(const ConditionalIterator &other) const { return other.itr != itr; }
  reference operator*() const { return *itr; }
  pointer   operator->() const { return &(*itr); }

  ConditionalIterator& operator++() {
    for (; ++itr != end;) {
      if (condition(*itr))
        break;
    }
    return *this;
  }
  ConditionalIterator operator++(int) {
    ConditionalIterator ret(*this);
    operator++();
    return ret;
  }
};

然后,您可以创建所需的conditional_beginconditional_end辅助函数。唯一的问题是std::vector::insert期望两个迭代器具有相同的类型。如果我们使用lambda作为条件,则这将成为条件迭代器类型的一部分。因此,我们需要将lambda传递给两个辅助函数,以便它们返回具有匹配类型的迭代器:

template <typename C, typename F> 
auto conditional_begin(const C &source, F f) {
  return ConditionalIterator<typename C::const_iterator, F>(source.begin(),
                                                            source.end(), f);
}

template <typename C, typename F> 
auto conditional_end(const C &source, F f) {
  return ConditionalIterator<typename C::const_iterator, F>(source.end(),
                                                            source.end(), f);
}

您可以使用这样的lambda进行呼叫:

auto condition = [](const auto &a) { return a != 3; };
target.insert(std::begin(target),
              conditional_begin(source, std::ref(condition)),
              conditional_end(source, std::ref(condition)));

Live demo

在这种情况下,我的crude tests显示比单纯使用copy_ifback_inserter的速度明显快得多,因为std::vector::insert首先计算出要分配多少内存插入。仅使用back_inserter会导致多个内存分配。性能上的差异将取决于评估条件的成本。通过使用count_if来保留足够的空间,然后再使用copy_if,可以达到相同的加速效果:

auto count = static_cast<size_t>(std::count_if(source.begin(),
                                 source.end(), condition));
target.reserve(target.size() + count);

std::copy_if(source.begin(),
             source.end(),
             std::back_inserter(target), condition);

Live demo

答案 2 :(得分:2)

由于范围即将标准化,因此可以使用range-v3(提案参考库)的替代方法:

#include <range/v3/view/concat.hpp>
#include <range/v3/view/filter.hpp>

using namespace ranges;

const std::vector<int> source{1, 3, 2, 3, 4, 5, 3};
const std::vector<int> target = view::concat(source,
    source | view::filter([](auto i){ return i != 3; }));