내가 작성하는 거의 모든 코드를 사용하여 궁극적으로 컬렉션 내부에 순진한 “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 ++ 관련 이해 와 거의 비슷해 보입니다 . 당신에게?