C ++로 내부에 “if”조건이있는 “for”루프를 피하려면 어떻게해야합니까? 사용하여 궁극적으로

내가 작성하는 거의 모든 코드를 사용하여 궁극적으로 컬렉션 내부에 순진한 “if”조건으로 끝나는 집합 감소 문제를 종종 다루고 있습니다. 다음은 간단한 예입니다.

for(int i=0; i<myCollection.size(); i++)
{
     if (myCollection[i] == SOMETHING)
     {
           DoStuff();
     }
}

함수형 언어를 사용하면 컬렉션을 다른 컬렉션으로 (쉽게) 축소하여 문제를 해결 한 다음 축소 된 집합에서 모든 작업을 수행 할 수 있습니다. 의사 코드에서 :

newCollection <- myCollection where <x=true
map DoStuff newCollection

그리고 C #과 같은 다른 C 변형에서는 다음과 같은 where 절을 사용하여 줄일 수 있습니다.

foreach (var x in myCollection.Where(c=> c == SOMETHING))
{
   DoStuff();
}

또는 더 나은 (적어도 내 눈에는)

myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));

물론 저는 많은 패러다임 믹싱과 주관적 / 의견 기반 스타일을하고 있지만 C ++에서이 선호하는 기술을 사용할 수있는 근본적인 무언가를 놓치고 있다는 느낌을받을 수밖에 없습니다. 누군가 나를 깨달을 수 있습니까?



답변

IMHO 내부에 if가있는 for 루프를 사용하는 것이 더 간단하고 읽기 쉽습니다. 그러나 이것이 귀찮다면 for_each_if아래와 같은 것을 사용할 수 있습니다 .

template<typename Iter, typename Pred, typename Op>
void for_each_if(Iter first, Iter last, Pred p, Op op) {
  while(first != last) {
    if (p(*first)) op(*first);
    ++first;
  }
}

사용 사례 :

std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });

라이브 데모


답변

Boost는 범위 기반으로 사용할 수있는 범위를 제공합니다. 범위들은 내부 데이터 구조를 복사하지 않도록, 그들은 단지 ‘도’를 제공하는 이점을 갖는다 (즉, begin(), end()범위 및 operator++(), operator==()반복자 용). 관심이있을 수 있습니다. http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html

#include <boost/range/adaptor/filtered.hpp>
#include <iostream>
#include <vector>

struct is_even
{
    bool operator()( int x ) const { return x % 2 == 0; }
};

int main(int argc, const char* argv[])
{
    using namespace boost::adaptors;

    std::vector<int> myCollection{1,2,3,4,5,6,7,8,9};

    for( int i: myCollection | filtered( is_even() ) )
    {
        std::cout << i;
    }
}

답변

새 알고리즘을 만드는 대신 수락 된 답변처럼 조건을 적용하는 함수가있는 기존 알고리즘을 사용할 수 있습니다.

std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });

또는 정말로 새로운 알고리즘을 원한다면 for_each반복 논리를 복제하는 대신 적어도 거기 에서 재사용 하십시오.

template<typename Iter, typename Pred, typename Op>
  void
  for_each_if(Iter first, Iter last, Pred p, Op op) {
    std::for_each(first, last, [&](auto& x) { if (p(x)) op(x); });
  }

답변

피하는 아이디어

for(...)
    if(...)

반 패턴으로서의 구조가 너무 넓습니다.

루프 내에서 특정 표현식과 일치하는 여러 항목을 처리하는 것은 완전히 괜찮으며 코드가 그보다 훨씬 명확해질 수 없습니다. 처리가 화면에 맞추기에는 너무 커지면 서브 루틴을 사용하는 좋은 이유가되지만 여전히 조건문은 루프 내부에 배치하는 것이 가장 좋습니다.

for(...)
    if(...)
        do_process(...);

훨씬 더 선호됩니다

for(...)
    maybe_process(...);

하나의 요소 만 일치 할 때 반 패턴이됩니다. 그러면 먼저 요소를 검색하고 루프 외부에서 처리를 수행하는 것이 더 명확하기 때문입니다.

for(int i = 0; i < size; ++i)
    if(i == 5)

이것의 극단적이고 명백한 예입니다. 더 미묘하고 더 일반적인 것은 다음과 같은 공장 패턴입니다.

for(creator &c : creators)
    if(c.name == requested_name)
    {
        unique_ptr<object> obj = c.create_object();
        obj.owner = this;
        return std::move(obj);
    }

본문 코드가 한 번만 실행된다는 것이 분명하지 않기 때문에 읽기 어렵습니다. 이 경우 조회를 분리하는 것이 좋습니다.

creator &lookup(string const &requested_name)
{
    for(creator &c : creators)
        if(c.name == requested_name)
            return c;
}

creator &c = lookup(requested_name);
unique_ptr obj = c.create_object();

여전히 if안에가 for있지만 컨텍스트에서 그것이 무엇을하는지 명확 해집니다. 조회가 변경되지 않는 한 (예 : a로 map) 이 코드를 변경할 필요가 없으며 , create_object()한 번만 호출 되는 것이 바로 루프 내부가 아닙니다.


답변

여기에 비교적 최소한의 빠른 filter기능이 있습니다.

술어가 필요합니다. iterable을 취하는 함수 객체를 반환합니다.

for(:)루프 에서 사용할 수있는 이터 러블을 반환합니다 .

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }

template<class It, class F>
struct filter_helper:range_t<It> {
  F f;
  void advance() {
    while(true) {
      (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      if (this->empty())
        return;
      if (f(*this->begin()))
        return;
    }
  }
  filter_helper(range_t<It> r, F fin):
    range_t<It>(r), f(std::move(fin))
  {
      while(true)
      {
          if (this->empty()) return;
          if (f(*this->begin())) return;
          (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      }
  }
};

template<class It, class F>
struct filter_psuedo_iterator {
  using iterator_category=std::input_iterator_tag;
  filter_helper<It, F>* 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<It>::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<class It, class F>
struct filter_range:
  private filter_helper<It, F>,
  range_t<filter_psuedo_iterator<It, F>>
{
  using helper=filter_helper<It, F>;
  using range=range_t<filter_psuedo_iterator<It, F>>;

  using range::begin; using range::end; using range::empty;

  filter_range( range_t<It> r, F f ):
    helper{{r}, std::forward<F>(f)},
    range{ {this, false}, {this, true} }
  {}
};

template<class F>
auto filter( F&& f ) {
    return [f=std::forward<F>(f)](auto&& r)
    {
        using std::begin; using std::end;
        using iterator = decltype(begin(r));
        return filter_range<iterator, std::decay_t<decltype(f)>>{
            range(begin(r), end(r)), f
        };
    };
};

나는 지름길을 택했다. 실제 라이브러리는 for(:)내가 한 자격을 갖춘 의사 파사드가 아닌 실제 반복자를 만들어야합니다 .

사용 시점에서 다음과 같이 보입니다.

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

꽤 멋지고

1
3
5

라이브 예 .

이런 종류의 작업을 수행하는 Rangesv3라는 C ++에 제안 된 추가 기능이 있습니다. boost또한 필터 범위 / 반복자를 사용할 수 있습니다. boost에는 위의 글을 훨씬 더 짧게 만드는 도우미도 있습니다.


답변

언급 할만큼 충분히 사용되었지만 아직 언급되지 않은 스타일은 다음과 같습니다.

for(int i=0; i<myCollection.size(); i++) {
  if (myCollection[i] != SOMETHING)
    continue;

  DoStuff();
}

장점 :

  • DoStuff();조건 복잡성이 증가 할 때 들여 쓰기 수준을 변경하지 않습니다 . 논리적으로 루프 DoStuff();의 최상위 수준에 있어야합니다 for.
  • 바로이 클리어하게 그 위에 루프 반복 SOMETHING컬렉션 S, 폐쇄 후 전혀 없다는 것을 확인하기 위해, 리더 없이도 }if블록.
  • 라이브러리 나 도우미 매크로 또는 함수가 필요하지 않습니다.

단점 :

  • continue다른 흐름 제어 문처럼, 너무 많은 사람들이 반대되는 어려운 추적 코드로 이어질 방법으로 오용됩니다 어떤 이들의 사용 :을 피가 몇 가지 따르는 것이 코딩의 유효한 스타일이 continue, 방지 break이외의 에서 함수의 끝이 아닌 다른 switch것을 피 return합니다.

답변

for(auto const &x: myCollection) if(x == something) doStuff();

for나에게 C ++ 관련 이해 와 거의 비슷해 보입니다 . 당신에게?