对于我写的几乎所有代码,我经常处理集合上的集合减少问题,最终最终会在其中出现天真的“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 ++中使用这种首选技术。有人可以启发我吗?
答案 0 :(得分:97)
恕我直言,使用带有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;
}
}
USECASE:
std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });
答案 1 :(得分:48)
Boost提供可以基于范围使用的范围。范围的优势在于它们不复制基础数据结构,它们仅提供“视图”(即begin()
,end()
用于范围和operator++()
,{{1对于迭代器来说)。这可能是您感兴趣的:http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html
operator==()
答案 2 :(得分:42)
您可以使用具有应用条件的函数的现有算法,而不是像接受的答案那样创建新算法:
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); });
}
答案 3 :(得分:21)
避免
的想法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
,但从上下文中可以清楚地了解它的作用,除非查找发生更改,否则无需更改此代码(例如{{1}很明显map
只被调用一次,因为它不在循环内。
答案 4 :(得分:17)
这是一个快速相对最小的filter
函数。
它需要一个谓词。它返回一个可迭代的函数对象。
它返回一个可以在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
也有可用的过滤范围/迭代器。提升也有帮助,使上述写作更短。
答案 5 :(得分:15)
一种足以提及但尚未提及的风格是:
func vkSdkAccessAuthorizationFinishedWithResult(result: VKAuthorizationResult!){
let tokenString = result.token.description
FIRAuth.auth()?.signInWithCustomToken(tokenString) { (user, error) in
// ...
}
}
优点:
ALTER TABLE your_db.your_table
MODIFY Your_enum_col ENUM('new_name_1','new_name_2','new_name_3');
的缩进级别。从逻辑上讲,for(int i=0; i<myCollection.size(); i++) {
if (myCollection[i] != SOMETHING)
continue;
DoStuff();
}
应该位于DoStuff();
循环的顶层,而且它是。DoStuff();
,而不要求读者在for
块的结束SOMETHING
之后验证没有任何内容。 缺点:
}
被滥用的方式导致难以理解的代码,以至于某些人反对任何使用它们:一种有效的编码风格,有些遵循避免if
,避免除continue
之外的continue
,除了在函数末尾之外避免break
。< / LI>
答案 6 :(得分:11)
for(auto const &x: myCollection) if(x == something) doStuff();
看起来非常像C ++特有的for
对我的理解。给你?
答案 7 :(得分:7)
如果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)。我和他们的邻居会有一些自由来提出更高级的约束。如果不需要,我们可以剥离内循环并重新制作外循环
for(int i = 0; i < myCollection.size(); i++)
times += (myCollection[i]==SOMETHING);
后跟
for(int i=0;i<times;i++)
DoStuff();
答案 8 :(得分:6)
另外,如果你不关心重新排序集合,std :: partition便宜。
#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
}
答案 9 :(得分:5)
我对上述解决方案的复杂性感到敬畏。我打算建议一个简单的#define foreach(a,b,c,d) for(a; b; c)if(d)
,但它有一些明显的缺陷,例如,你必须记住在循环中使用逗号而不是分号,并且你不能在{{1中使用逗号运算符}或a
。
c
答案 10 :(得分:2)
在i:s很重要的情况下的另一种解决方案。这个构建一个列表,填充要为其调用doStuff()的索引。再一次,重点是避免分支并将其换成可管道的算术成本。
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.
“神奇”线是缓冲加载线,它通过算术计算保持值并保持在位或计算位置和增加值。因此,我们将一个潜在的分支换成一些逻辑和算术,也许还有一些缓存命中。这种情况有用的典型情况是doStuff()执行少量可管道计算,并且调用之间的任何分支都可能会中断这些管道。
然后循环遍历缓冲区并运行doStuff()直到我们到达cnt。这次我们将当前i存储在缓冲区中,以便我们可以在调用doStuff()时使用它。如果需要的话。
答案 11 :(得分:1)
一个人可以将您的代码模式描述为对某个范围的子集应用某些功能,换句话说:将其应用于对整个范围应用过滤器的结果。
这可以通过Eric Neibler的ranges-v3 library以最直接的方式实现。尽管有点麻烦,但是因为您要使用索引:
using namespace ranges;
auto mycollection_has_something =
[&](std::size_t i) { return myCollection[i] == SOMETHING };
auto filtered_view =
views::iota(std::size_t{0}, myCollection.size()) |
views::filter(mycollection_has_something);
for (auto i : filtered_view) { DoStuff(); }
但是,如果您愿意放弃索引,您将获得:
auto is_something = [&SOMETHING](const decltype(SOMETHING)& x) { return x == SOMETHING };
auto filtered_collection = myCollection | views::filter(is_something);
for (const auto& x : filtered_collection) { DoStuff(); }
这是更好的恕我直言。
PS-Ranges库主要进入C ++ 20中的C ++标准。
答案 12 :(得分:0)
我只会提到Mike Acton,他肯定会说:
如果必须这样做,则数据有问题。对数据进行排序!