过滤STL容器的现代方法?

ATV*_*ATV 70 c++ stl c++11

经过多年的C#回到C++之后,我想知道现代 - 读取:C++ 11 - 过滤数组的方式是什么,即我们如何实现类似于Linq查询的东西:

var filteredElements = elements.Where(elm => elm.filterProperty == true);
Run Code Online (Sandbox Code Playgroud)

为了过滤元素的向量(strings为了这个问题)?

我真诚地希望现在可以取代需要定义显式方法的旧STL样式算法(甚至扩展boost::filter_iterator)?

Seb*_*ann 82

请参阅cplusplus.com中的示例std::copy_if:

std::vector<int> foo = {25,15,5,-5,-15};
std::vector<int> bar;

// copy only positive numbers:
std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;} );
Run Code Online (Sandbox Code Playgroud)

std::copy_if计算foo此处每个元素的lambda表达式,如果返回true则将值复制到bar.

std::back_inserter允许我们实际上在插入的最后新元素bar(使用push_back())同一个迭代,而不必首先将它调整到所需的大小.

  • 这真的是最接近C++必须提供的LINQ吗?这是渴望(IOW不懒惰),而且非常冗长. (21认同)
  • @Paranaix一切都可以说是语法糖而不是装配.关键是,当使用基本操作(如过滤器)以可读方式清楚地组合算法时,不要编写for循环.许多语言都提供了这样的功能 - 不幸的是,在C++中,它仍然是kludgy. (9认同)
  • 汇编只是机器代码的语法糖。 (3认同)

djh*_*987 34

如果您实际上不需要列表的新副本,则更有效的方法是remove_if实际从原始容器中删除元素.

  • 对于vector,至少,`remove_if`不会改变`size()`.[你需要用'擦除'来链接它](http://cpp.sh/42g7w). (9认同)
  • @ATV我特别喜欢`remove_if`因为它是在存在突变的情况下使用过滤器的方式,这比复制一个全新的列表要快.如果我在C++中进行过滤,我会在`copy_if`上使用它,所以我认为它会增加. (6认同)
  • @rampion是的..擦除/删除。如今,另一种使我经常感觉像在用C ++(相对于现代语言)工作时在磁带上打孔的美感;-) (2认同)
  • 显式擦除是一个功能。您不必在所有情况下都进行擦除。有时迭代器足以继续。在这种情况下,隐式擦除会产生不必要的开销。此外,并非每个容器都可以调整大小。例如 std::array 根本没有擦除方法。 (2认同)

use*_*832 23

我认为Boost.Range也值得一提.结果代码非常接近原始代码:

#include <boost/range/adaptors.hpp>

// ...

using boost::adaptors::filtered;
auto filteredElements = elements | filtered([](decltype(elements)::value_type const& elm)
    { return elm.filterProperty == true; });
Run Code Online (Sandbox Code Playgroud)

唯一的缺点是必须明确声明lambda的参数类型.我使用了decltype(elements):: value_type,因为它避免了拼写出确切的类型,并且还增加了一些通用性.或者,使用C++ 14的多态lambda,可以将类型简单地指定为auto:

auto filteredElements = elements | filtered([](auto const& elm)
    { return elm.filterProperty == true; });
Run Code Online (Sandbox Code Playgroud)

filteredElements将是一个适合遍历的范围,但它基本上是原始容器的视图.如果您需要的是另一个装满满足条件的元素副本的容器(这样它与原始容器的生命周期无关),它可能看起来像:

using std::back_inserter; using boost::copy; using boost::adaptors::filtered;
decltype(elements) filteredElements;
copy(elements | filtered([](decltype(elements)::value_type const& elm)
    { return elm.filterProperty == true; }), back_inserter(filteredElements));
Run Code Online (Sandbox Code Playgroud)


pjm*_*pjm 9

我对C++等效C#的建议

var filteredElements = elements.Where(elm => elm.filterProperty == true);
Run Code Online (Sandbox Code Playgroud)

定义传递lambda谓词以进行过滤的模板函数.模板函数返回过滤结果.例如:

template<typename T>
vector<T> select_T(vector<T> inVec, function<bool(const T&)> predicate)
{
  vector<T> result;
  copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
  return result;
}
Run Code Online (Sandbox Code Playgroud)

使用 - 给出一个简单的例子:

std::vector<int> mVec = {1,4,7,8,9,0};

// filter out values > 5
auto gtFive = select_T<int>(mVec, [](auto a) {return (a > 5); });

// or > target
int target = 5;
auto gt = select_T<int>(mVec, [target](auto a) {return (a > target); });
Run Code Online (Sandbox Code Playgroud)


Ale*_* P. 8

根据下划线-d的建议改进了pjm代码:

template <typename Cont, typename Pred>
Cont filter(const Cont &container, Pred predicate) {
    Cont result;
    std::copy_if(container.begin(), container.end(), std::back_inserter(result), predicate);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

用法:

std::vector<int> myVec = {1,4,7,8,9,0};

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Run Code Online (Sandbox Code Playgroud)


L. *_* F. 8

In C++20, use filter view from the ranges library:

vec | view::filter([](int a){ return a % 2 == 0; })
Run Code Online (Sandbox Code Playgroud)

lazily returns the even elements in vec.

(See [range.adaptor.object]/4 and [range.filter])

  • 您应该提供编译答案所需的 #include 和 use 命名空间。另外,到目前为止什么编译器支持这个? (5认同)
  • @gsimard 现在好多了? (4认同)
  • 如果有人尝试在 macOS 中执行此操作:截至 2020 年 5 月,[libc++](https://libcxx.llvm.org/cxx2a_status.html) 不支持此操作。 (4认同)
  • 这需要被标记为 2021 年底的答案。一种更简洁(并且可以说“非常”实用:管道运算符也在 Haskell 中使用)的过滤方式。 (4认同)