过滤的向量的迭代器?

时间:2017-06-04 04:26:52

标签: c++ lambda iterator containers

假设我有一个spot_deals SpotDeal的向量,它是一个类

class SpotDeal
{
public:
    int deal_id_; // primary key, and vector is sorted by id
    string ccy_pair_; // ccy pair, e.g. GBPUSD, AUDUSD
    double amount_;
}

假设我需要将spot_deals的两个子集传递给函数foo进行某些计算。然而,我可以复制,这将导致记忆和时间。 Acutally foo只需要交易的迭代器。那么我可以制作vecto<SpotDeal>的2个迭代器,即it1it2并将它们传递给foo吗?

spot_deals的两个子集可以通过ccy_pair_进行过滤,例如英镑兑美元和澳元兑美元的交易,或其他条件。所以我正在寻找一种可以定义由向量和lambda函数定义的迭代器的方法(虽然可以等效于仿函数)。

有没有办法编写辅助函数make_filtered_iterator,以便我可以使用下面的内容?

auto it1 = make_filtered_iterator(spot_deals, filter_lambda1);
auto it2 = make_filtered_iterator(spot_deals, filter_lambda2);
foo(it1, it2);

5 个答案:

答案 0 :(得分:4)

是的,可以创建迭代器类型。但是,我怀疑你的问题是XY问题的一个例子(https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) - 你想找到一种方法在向量(X)的两个子集上进行不同的操作,已经确定解决方案必须涉及实现一个特殊的 - 目的迭代器(Y),并询问如何做Y而不是X.我将提供一个选项来做X而不需要做Y.

我建议使用标准算法std::stable_partition()将容器分成两个范围会更容易。

 auto false_partition = std::stable_partition(your_vector.begin(), your_vector.end(), your_filter);

向量的begin()end()迭代器不会更改(即不会失效),但它们之间的元素会重新组织为两个范围,例如{{1}的元素} your_filter返回true返回your_filter的元素集之前的false。因此,false_partition同时也是过去的结果&#34;第一个范围的迭代器和第二个范围的开头。每个范围中元素的顺序与原始向量中的元素顺序相同。

这些可以使用如下

 //   a loop to operates on the elements for which your_filter returned true

 for (auto i = your_vector.begin(); i != false_partition; ++i)
 {
      // do whatever
 }

 //   a loop to operates on the elements for which your_filter returned false

 for (auto i = false_partition; i != your_vector.end(); ++i)
 {
      // do whatever
 }

在C ++ 11之前,auto关键字可以替换为适当的迭代器类型(例如std::vector<int>::iteratorstd::vector<int>::const_iterator,具体取决于您是否希望使用迭代器更改元素)。

答案 1 :(得分:2)

我不会使用指向原始向量的迭代器,因为它们无法传达子集的大小。 (基本上,每个子集需要一个迭代器来表示子集的结尾。)从 C++20 开始,我将使用 Ranges library 中的 ranges,如上所述在 v.oddou's 答案中。更具体地说,对于您的用例,我将使用范围适配器 std::views::filter,如下所示:

auto gbpusd = [](const auto& sd) { return sd.ccy_pair_ == "GBPUSD"; };
auto audusd = [](const auto& sd) { return sd.ccy_pair_ == "AUDUSD"; };

auto range1 = spot_deals | std::views::filter(gbpusd);
auto range2 = spot_deals | std::views::filter(audusd);

foo(range1, range2);

此解决方案不会为过滤的现货交易创建临时向量,因为视图适配器创建的范围不包含元素。结果范围 range1range2 只是向量 spot_deals 的视图,但具有自定义的迭代行为。

foo() 的声明有点棘手,因为范围的数据类型相当复杂。因此,我将使用占位符类型 auto 作为函数参数,从而使 foo() 成为 function template:

void foo(auto& r1, auto& r2) {
    for (auto const& sd : r1)
        std::cout << sd.deal_id_ << std::endl;
    for (auto const& sd : r2)
        std::cout << sd.amount_ << std::endl;
}

(或者,您可以通过对 spot_deals 的引用传递 foo() 并在 foo() 中声明过滤范围。)

Code on Wandbox

答案 2 :(得分:1)

答案肯定是“是的”。 STL样式的C ++迭代器可以做各种各样的技巧。一个常见但基本的方法是为std::map创建一个迭代器,当取消引用时只给出键或值。

在您的特定情况下,一个简单的实现可能是这样的:

template <typename BaseIterator>
struct filtered_iterator : BaseIterator
{
    typedef std::function<bool (const value_type&)> filter_type;

    filtered_iterator() = default;
    filtered_iterator(filter_type filter, BaseIterator base, BaseIterator end = {})
        : BaseIterator(base), _end(end), _filter(filter_type) {
        while (*this != _end && !_filter(**this)) {
            ++*this;
        }
    }

    filtered_iterator& operator++() {
        do {
            BaseIterator::operator++();
        } while (*this != _end && !_filter(**this));
    }

    filtered_iterator operator++(int) {
        filtered_iterator copy = *this;
        ++*this;
        return copy;
    }

private:
    BaseIterator _end;
    filter_type _filter;
};

template <typename BaseIterator>
filtered_iterator<BaseIterator> make_filtered_iterator(
        typename filtered_iterator<BaseIterator>::filter_type filter,
        BaseIterator base, BaseIterator end = {}) {
    return {filter, base, end};
}

我为end设置了一个默认值,因为通常你可以使用默认构造的迭代器。但在某些情况下,您可能只想过滤容器的一个子集,在这种情况下,指定结尾会使其变得容易。

答案 3 :(得分:1)

我可能建议对此问题的读者使用Eric Nibbler的rangev3速成课程,因为这是C ++ 20标准库采用的范例。

https://github.com/ericniebler/range-v3

如何进行过滤器迭代:

for (auto element : spot_deals | views::filter([](auto i) { return condition(i); }))
{ //....
}

答案 4 :(得分:0)

偶然的机会,我最近解决了这个问题。事实证明,过滤是容器上相当多的一些操作中最复杂的,并且也包含最多的陷阱。

template<typename Range, typename Pred>
class filter
{
public:
    friend class const_iterator;
    class const_iterator : public std::iterator_traits<typename Range::const_iterator>
    {
        using underlying = typename Range::const_iterator;
    public:
        auto operator*() {return *u;}
        const_iterator& operator++()
        {
            ++u;
            normalize();
            return *this;
        }
        const_iterator operator++(int)
        {
            auto t = *this;
            u++;
            normalize();
            return t;
        }
        bool operator==(const const_iterator& rhs) const {return u == rhs.u;}
        bool operator!=(const const_iterator& rhs) const {return !(*this == rhs);}
    private:
        friend filter;
        const_iterator(underlying u, const filter& f) : u{std::move(u)}, f{f} {normalize();}
        void normalize()
        {
            for(; u != f.r.end() && !f.p(*u); u++);
        }

        underlying u;
        const filter& f;
    };

    filter(const Range& r, const Pred& p) : r{r}, p{p} {}

    auto begin() const {return const_iterator{r.begin(), *this};}
    auto end() const {return const_iterator{r.end(), *this};}

private:
    const Range& r;
    const Pred& p;
};

我们将它用作(使用c ++ 17指南)

vector<int> v{1, 2, 3, 4, 5};
auto f = filter(v, [](int x){return x & 1;});
for(auto i : f)
    // all i in v that is odd

让我解释一下陷阱:

  • 第一个元素可能会被过滤掉,*r.begin()可能不是过滤范围内的元素。这意味着必须在构造上检查迭代器。
  • r.end()可能无效而不会使其他迭代器失效,也就是说,在任何迭代器中保存r.end()的副本以进行比较是一个逻辑错误。
  • operator==不是很简单,两个底层迭代器引用原始范围中的不同元素可能是指过滤视图中的相同元素,因为过滤掉的元素不算作元素。