过滤STL容器的现代方法?


102

在使用了C#多年之后,我又回到C ++了,我想知道现代的方式-阅读:C ++ 11-筛选数组的方式,即如何实现类似于此Linq查询的内容:

var filteredElements = elements.Where(elm => elm.filterProperty == true);

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

我衷心希望现在已取代需要定义显式方法的旧STL样式算法(甚至是扩展boost::filter_iterator)。


这会检索filterProperty设置为的所有元素true吗?
约瑟夫·曼斯菲尔德

对不起,是的。一些通用的过滤条件..
亚视

3
也有一些库试图模拟.NET的LINQ方法:Linq ++cpplinq。我没有与他们合作,但我猜他们将支持STL容器。
德克2014年

1
您应该对自己想要的东西更加清楚,因为能同时使用C ++和C#的人员很少。描述您想要它做什么。
Yakk-Adam Nevraumont 2014年

Answers:


124

请参阅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;} );

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

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


30
这真的是C ++必须提供的与LINQ最接近的吗?这很渴望(IOW并不懒惰),而且很冗长。
usr

1
@usr它的IMO语法糖,一个简单的for循环也可以完成这项工作(并且通常允许避免复制)。
塞巴斯蒂安·霍夫曼

1
OP的示例不使用任何LINQ语法糖。好处是懒惰的评估和可组合性。
usr

1
@usr仍然可以通过简单的for循环轻松实现,std::copy_if仅不过是for循环
Sebastian Hoffmann 2014年

15
@Paranaix一切都可以说只是汇编上的语法糖。关键是,不要编写循环,而是可以使用原始操作(例如过滤器)以可读的方式清楚地组成算法。许多语言都提供了这样的功能-不幸的是,在C ++中,它仍然很笨拙。
BartoszKP 2014年

48

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


7
remove_if特别喜欢@ATV ,因为它是在出现突变时使用过滤器的方法,比复制整个新列表要快。如果我使用C ++进行过滤,则可以在上使用它copy_if,因此我认为它会增加。
djhaskin987 '16

16
对于vector,至少remove_if不会更改size()为此,您需要将其链接起来erase
猖ion

5
@rampion是的..擦除/删除。如今,另一种使我经常感觉像在用C ++(相对于现代语言)工作时在磁带上打孔的美人;-)
ATV

1
显式擦除是一项功能。您不必在所有情况下都擦除。有时,迭代器足以继续。在这种情况下,隐式擦除会产生不必要的开销。此外,并非每个容器都可调整大小。例如,std :: array根本没有擦除方法。
马丁·费斯

35

在C ++ 20中,使用Ranges库中的过滤器视图:(需要#include <ranges>

// namespace views = std::ranges::views;
vec | views::filter([](int a){ return a % 2 == 0; })

懒惰地返回中的偶数元素vec

(请参阅[range.adaptor.object] / 4[range.filter]


GCC 10(实时演示)已支持此功能。对于Clang和较早版本的GCC,也可以将原始range-v3库与#include <range/v3/view/filter.hpp>(或#include <range/v3/all.hpp>)和ranges::views命名空间一起使用,而不是std::ranges::viewslive demo)。


您应该提供#include并使用编译答案所需的名称空间。另外,到目前为止,什么编译器支持此功能?
gsimard'4

2
@gsimard现在好了吗?
LF

2
如果有人尝试在macOS中执行此操作:从2020年5月开始,libc ++不支持此功能。
dax

25

我认为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; });

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

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

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));

12

我对相当于C#的C ++的建议

var filteredElements = elements.Where(elm => elm.filterProperty == true);

定义一个模板函数,向其传递lambda谓词以进行过滤。模板函数返回过滤后的结果。例如:

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

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

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); });

11

根据下划线-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;
}

用法:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.