我怎样才能避免“for”循环与他们在内部的“if”条件与C + +?
几乎所有我写的代码,我经常处理集合的集合减less问题,最终最终会以它们内部天真的“if”条件结束。 这是一个简单的例子:
for(int i=0; i<myCollection.size(); i++) { if (myCollection[i] == SOMETHING) { DoStuff(); } }
使用function性语言,我可以通过将收集减less到另一个收集(轻松)来解决问题,然后对缩减集执行所有操作。 在伪代码中:
newCollection <- myCollection where <x=true map DoStuff newCollection
而在其他C变种,如C#,我可以减less一个where子句,如
foreach (var x in myCollection.Where(c=> c == SOMETHING)) { DoStuff(); }
或者更好(至less在我眼中)
myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));
不可否认,我正在做很多混合范式和基于主观/意见的风格,但是我不禁觉得我错过了一些真正的基础,可以让我在C ++中使用这个首选的技术。 有人能教导我吗?
恕我直言,这是更直接和更易于使用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; } }
正如接受的答案所做的那样,您可以使用现有的algorithm来应用条件,而不是创build新的algorithm:
std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });
或者如果你真的想要一个新的algorithm,至less重用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(...)
作为反模式的构造过于宽泛。
从循环内部处理与某个expression式相匹配的多个项目是完全正确的,代码不会比这更清晰。 如果处理增长太大而不适合在屏幕上,这是使用子程序的一个很好的理由,但条件最好放置在循环内,即
for(...) if(...) do_process(...);
是非常可取的
for(...) maybe_process(...);
当只有一个元素匹配时它变成一个反模式,因为那么首先search元素并在循环之外执行处理会更清楚。
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();
在for
还有一个if
,但是从上下文来看,它变得很清楚,除非查找改变(例如map
到map
),否则不需要改变这个代码,并立即清楚地知道create_object()
被调用只有一次,因为它不在一个循环内。
这是一个相对简单的filter
function。
它需要一个谓词。 它返回一个需要迭代的函数对象。
它返回一个可用于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(:)
合格伪fascades。
在使用的时候,它看起来像这样:
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
现场示例 。
除了C ++之外,还有一个名为Rangesv3的提议,它可以完成这种事情。 boost
也有可用的filter范围/迭代器。 助推器也有助手,使上面写得更短。
一个足以提及但尚未被提及的风格是:
for(int i=0; i<myCollection.size(); i++) { if (myCollection[i] != SOMETHING) continue; DoStuff(); }
优点:
- 不改变
DoStuff();
的缩进级别DoStuff();
条件复杂度增加时。 逻辑上,DoStuff();
应该在for
循环的顶层,而且是。 - 立即清楚地表明,循环遍历集合中的
SOMETHING
,而不需要读者validationif
块closures后没有任何东西。 - 不需要任何库或帮助macros或函数。
缺点:
-
continue
像其他stream程控制语句一样被滥用,导致难以遵循的代码太多,以至于有人反对使用它们:有一种有效的编码风格,有些遵循避免continue
,避免除了在一个switch
之外的其他switch
,它可以避免在函数结束时return
。
for(auto const &x: myCollection) if(x == something) doStuff();
看起来非常像一个C ++ – 专门for
我理解。 给你?
如果DoStuff()将在未来以某种方式依赖,那么我会提出这个保证无分支的位掩码变体。
unsigned int times = 0; const int kSize = sizeof(unsigned int)*8; for(int i = 0; i < myCollection.size()/kSize; i++){ unsigned int mask = 0; for (int j = 0; j<kSize; j++){ mask |= (myCollection[i*kSize+j]==SOMETHING) << j; } times+=popcount(mask); } for(int i=0;i<times;i++) DoStuff();
在哪里popcount是任何函数进行人口计数(计数位数= 1)。 我和他们的邻居会有一些自由来提出更高级的限制。 如果不需要,我们可以去掉内部循环并重新构build外部循环
for(int i = 0; i < myCollection.size(); i++) times += (myCollection[i]==SOMETHING);
其次是一个
for(int i=0;i<times;i++) DoStuff();
另外,如果你不关心重新sorting集合,std ::分区是便宜的。
#include <iostream> #include <vector> #include <algorithm> #include <functional> void DoStuff(int i) { std::cout << i << '\n'; } int main() { using namespace std::placeholders; std::vector<int> v {1, 2, 5, 0, 9, 5, 5}; const int SOMETHING = 5; std::for_each(v.begin(), std::partition(v.begin(), v.end(), std::bind(std::equal_to<int> {}, _1, SOMETHING)), // some condition DoStuff); // action }
我敬畏上述解决scheme的复杂性。 #define foreach(a,b,c,d) for(a; b; c)if(d)
我会提出一个简单的#define foreach(a,b,c,d) for(a; b; c)if(d)
但是它有一些明显的缺陷,例如,你必须记住使用逗号在循环中使用分号,并且不能在a
或c
使用逗号运算符。
#include <list> #include <iostream> using namespace std; #define foreach(a,b,c,d) for(a; b; c)if(d) int main(){ list<int> a; for(int i=0; i<10; i++) a.push_back(i); for(auto i=a.begin(); i!=a.end(); i++) if((*i)&1) cout << *i << ' '; cout << endl; foreach(auto i=a.begin(), i!=a.end(), i++, (*i)&1) cout << *i << ' '; cout << endl; return 0; }
另一个解决scheme,如果我是重要的。 这个构build了一个列表,填充要调用doStuff()的索引。 主要的一点是避免分支,并将其交换为stream水线算术成本。
int buffer[someSafeSize]; int cnt = 0; // counter to keep track where we are in list. for( int i = 0; i < container.size(); i++ ){ int lDecision = (container[i] == SOMETHING); buffer[cnt] = lDecision*i + (1-lDecision)*buffer[cnt]; cnt += lDecision; } for( int i=0; i<cnt; i++ ) doStuff(buffer[i]); // now we could pass the index or a pointer as an argument.
“神奇”线是算术计算的缓冲载入线,以保持数值并保持位置或计数位置和增加数值。 所以我们交换一些逻辑和algorithm的潜在分支,也许一些caching命中。 一个典型的情况是,如果doStuff()执行less量stream水线计算,而且两个调用之间的任何分支都可能中断这些stream水线。
然后,只需循环缓冲区并运行doStuff(),直到到达cnt。 这次我们将把当前存储在缓冲区中的数据存储起来,这样我们就可以在调用doStuff()的时候使用它,如果需要的话。