Cách hiện đại để lọc thùng chứa STL?


99

Trở lại với C ++ sau nhiều năm sử dụng C # Tôi đã tự hỏi cách lọc mảng hiện đại - đọc: C ++ 11 - sẽ như thế nào, tức là làm thế nào chúng ta có thể đạt được điều gì đó tương tự như truy vấn Linq này:

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

Để lọc một vectơ gồm các phần tử ( stringsvì mục đích của câu hỏi này)?

Tôi thực sự hy vọng các thuật toán kiểu STL cũ (hoặc thậm chí các phần mở rộng như boost::filter_iterator) yêu cầu các phương pháp rõ ràng được xác định sẽ được thay thế ngay bây giờ?


Điều này có truy xuất tất cả các phần tử đã filterPropertyđặt thành truekhông?
Joseph Mansfield

Xin lỗi, vâng. Một số tiêu chí bộ lọc chung ..
ATV

3
Cũng có một số thư viện cố gắng mô phỏng các phương thức LINQ của .NET: Linq ++cpplinq . Tôi chưa làm việc với họ nhưng tôi đoán là họ hỗ trợ các thùng chứa STL.
Dirk

1
Bạn nên rõ ràng hơn về những gì bạn muốn, vì nhóm người có năng lực về cả C ++ và C # là rất nhỏ. Mô tả những gì bạn muốn nó làm.
Yakk - Adam Nevraumont

Câu trả lời:


119

Xem ví dụ từ cplusplus.com cho 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đánh giá biểu thức lambda cho mọi phần tử ở foođây và nếu nó trả về truenó sẽ sao chép giá trị tới bar.

Các std::back_inserterphép chúng ta thực sự chèn yếu tố mới vào cuối bar(sử dụng push_back()) với một iterator mà không cần phải thay đổi kích thước với kích thước yêu cầu đầu tiên.


30
Đây có thực sự là cách gần nhất với LINQ mà C ++ cung cấp không? Điều này là háo hức (IOW không lười biếng), và rất dài dòng.
usr

1
@usr Đường cú pháp IMO của nó, một vòng lặp for đơn giản hoạt động tốt (và thường cho phép một người tránh sao chép).
Sebastian Hoffmann

1
Ví dụ OPs không sử dụng bất kỳ đường cú pháp LINQ nào. Lợi ích là đánh giá lười biếng và khả năng kết hợp.
usr

1
@usr nào vẫn có thể đạt được bằng cách đơn giản cho vòng một cách dễ dàng, std::copy_ifkhông hơn một cho vòng lặp
Sebastian Hoffmann

15
@Paranaix Mọi thứ có thể được cho là chỉ là đường cú pháp qua lắp ráp. Vấn đề là, không nên viết vòng lặp for, khi một thuật toán có thể được cấu tạo rõ ràng theo cách có thể đọc được bằng cách sử dụng các phép toán nguyên thủy (như bộ lọc). Nhiều ngôn ngữ cung cấp tính năng như vậy - không may là trong C ++, nó vẫn còn khó hiểu.
BartoszKP

47

Một cách tiếp cận hiệu quả hơn, nếu bạn không thực sự cần một bản sao mới của danh sách, là remove_ifthực sự loại bỏ các phần tử khỏi vùng chứa ban đầu.


7
remove_ifĐặc biệt, tôi thích @ATV vì đó là cách sử dụng bộ lọc khi có đột biến, nhanh hơn việc sao chép một danh sách hoàn toàn mới. Nếu tôi đang thực hiện bộ lọc trong C ++, tôi sẽ sử dụng nó hơn copy_if, vì vậy tôi nghĩ rằng nó bổ sung.
djhaskin987

16
Đối với vectơ, ít nhất, remove_ifkhông thay đổi size(). Bạn sẽ cần phải chuỗi nó erasecho điều đó .
rapter

5
@raosystem Yeah .. xóa / xóa. Một vẻ đẹp mà thường xuyên khiến tôi cảm thấy như tôi là lỗ đấm trong một băng khi làm việc trong C ++ (như trái ngược với ngôn ngữ hiện đại) những ngày này ;-)
ATV

1
Xóa rõ ràng là một tính năng. Bạn không phải xóa trong mọi trường hợp. Đôi khi các trình lặp là đủ để tiếp tục. Trong những trường hợp như vậy, việc xóa ngầm sẽ tạo ra chi phí không cần thiết. Hơn nữa, không phải mọi vùng chứa đều có thể thay đổi kích thước. std :: array chẳng hạn không có phương thức xóa nào cả.
Martin Fehrs

33

Trong C ++ 20, sử dụng chế độ xem bộ lọc từ thư viện phạm vi: (yêu cầu #include <ranges>)

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

lười biếng trả về các phần tử chẵn trong vec.

(Xem [range.adaptor.object] / 4[range.filter] )


Điều này đã được hỗ trợ bởi GCC 10 ( bản demo trực tiếp ). Đối với Clang và các phiên bản cũ hơn của GCC, thư viện range-v3 ban đầu cũng có thể được sử dụng với #include <range/v3/view/filter.hpp>(hoặc #include <range/v3/all.hpp>) và ranges::viewskhông gian tên thay vì std::ranges::views( bản trình diễn trực tiếp ).


Bạn nên cung cấp #include và sử dụng không gian tên cần thiết để biên dịch câu trả lời của bạn. Ngoài ra, trình biên dịch nào hỗ trợ điều này cho đến ngày nay?
gsimard

2
@gsimard Tốt hơn bây giờ?
LF

1
Nếu ai đó cố gắng thực hiện việc này trong macOS: Kể từ tháng 5 năm 2020, libc ++ không hỗ trợ điều này.
dax

25

Tôi nghĩ Boost.Range cũng xứng đáng được nhắc đến. Mã kết quả khá gần với mã gốc:

#include <boost/range/adaptors.hpp>

// ...

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

Nhược điểm duy nhất là phải khai báo rõ ràng kiểu tham số của lambda. Tôi đã sử dụng kiểu khai báo (phần tử) :: value_type vì nó tránh phải viết chính xác kiểu và cũng thêm một loại chung chung. Ngoài ra, với lambdas đa hình của C ++ 14, kiểu có thể được chỉ định đơn giản là auto:

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

FilterElements sẽ là một phạm vi, phù hợp để duyệt, nhưng về cơ bản nó là một chế độ xem của vùng chứa ban đầu. Nếu những gì bạn cần là một vùng chứa khác chứa đầy các bản sao của các phần tử đáp ứng tiêu chí (để nó độc lập với thời gian tồn tại của vùng chứa ban đầu), nó có thể giống như sau:

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

Đề xuất của tôi cho C ++ tương đương với C #

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

Xác định một hàm mẫu mà bạn chuyển một vị từ lambda để thực hiện lọc. Hàm mẫu trả về kết quả đã lọc. ví dụ:

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

để sử dụng - đưa ra một ví dụ nhỏ:

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

Cải thiện mã pjm theo các đề xuất gạch dưới-d :

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

Sử dụng:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.