How can I avoid “for” loops with an “if” condition inside them with C++?

前端 未结 13 1702
滥情空心
滥情空心 2021-01-30 03:31

With almost all code I write, I am often dealing with set reduction problems on collections that ultimately end up with naive \"if\" conditions inside of them. Here\'s a simple

13条回答
  •  别那么骄傲
    2021-01-30 04:09

    Here is a quick relatively minimal filter function.

    It takes a predicate. It returns a function object that takes an iterable.

    It returns an iterable that can be used in a for(:) loop.

    template
    struct range_t {
      It b, e;
      It begin() const { return b; }
      It end() const { return e; }
      bool empty() const { return begin()==end(); }
    };
    template
    range_t range( It b, It e ) { return {std::move(b), std::move(e)}; }
    
    template
    struct filter_helper:range_t {
      F f;
      void advance() {
        while(true) {
          (range_t&)*this = range( std::next(this->begin()), this->end() );
          if (this->empty())
            return;
          if (f(*this->begin()))
            return;
        }
      }
      filter_helper(range_t r, F fin):
        range_t(r), f(std::move(fin))
      {
          while(true)
          {
              if (this->empty()) return;
              if (f(*this->begin())) return;
              (range_t&)*this = range( std::next(this->begin()), this->end() );
          }
      }
    };
    
    template
    struct filter_psuedo_iterator {
      using iterator_category=std::input_iterator_tag;
      filter_helper* helper = nullptr;
      bool m_is_end = true;
      bool is_end() const {
        return m_is_end || !helper || helper->empty();
      }
    
      void operator++() {
        helper->advance();
      }
      typename std::iterator_traits::reference
      operator*() const {
        return *(helper->begin());
      }
      It base() const {
          if (!helper) return {};
          if (is_end()) return helper->end();
          return helper->begin();
      }
      friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
        if (lhs.is_end() && rhs.is_end()) return true;
        if (lhs.is_end() || rhs.is_end()) return false;
        return lhs.helper->begin() == rhs.helper->begin();
      }
      friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
        return !(lhs==rhs);
      }
    };
    template
    struct filter_range:
      private filter_helper,
      range_t>
    {
      using helper=filter_helper;
      using range=range_t>;
    
      using range::begin; using range::end; using range::empty;
    
      filter_range( range_t r, F f ):
        helper{{r}, std::forward(f)},
        range{ {this, false}, {this, true} }
      {}
    };
    
    template
    auto filter( F&& f ) {
        return [f=std::forward(f)](auto&& r)
        {
            using std::begin; using std::end;
            using iterator = decltype(begin(r));
            return filter_range>{
                range(begin(r), end(r)), f
            };
        };
    };
    

    I took short cuts. A real library should make real iterators, not the for(:)-qualifying pseudo-fascades I did.

    At point of use, it looks like this:

    int main()
    {
      std::vector test = {1,2,3,4,5};
      for( auto i: filter([](auto x){return x%2;})( test ) )
        std::cout << i << '\n';
    }
    

    which is pretty nice, and prints

    1
    3
    5
    

    Live example.

    There is a proposed addition to C++ called Rangesv3 which does this kind of thing and more. boost also has filter ranges/iterators available. boost also has helpers that make writing the above much shorter.

提交回复
热议问题