简体   繁体   中英

Can we get an iterator that filters a vector from a predicate in C++?

Is it possible to get an iterator over a vector that filters some element with a predicate, ie showing a view of the vector?

I think remove_if does something similar but I have not found whether I can use it as I want to or not.

Something like:

auto it = filter(vec.begin(), vec.end(), predicate);
// I can reuse the iterator like:
for (auto i = it; i != vec.end(); i++)
    // ...

Edit: (A bit more context to get the best answer) I am doing a lot of queries in an sqlite database of log data in order to print a report.

The performances are not good at the moment because of the number of request needed. I believe querying once the database and storing the result in a vector of smart pointers ( unique_ptr if possible), then querying the vector with pure C++ may be faster.

Using copy_if is a good way to do the queries, but I don't need to copy everything and it might cost too much at the end (not sure about that), I should have mentioned than the data are immutable in my case.

As @Jarod42 mentioned in the comments one solution would be using ranges:

#include <algorithm>
#include <iostream>
#include <vector>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp>

int main()
{

   std::vector<int> numbers = { 1, 2, 3 ,4, 5 };
   auto predicate = [](int& n){ return n % 2 == 0; };
   auto evenNumbers = numbers | ranges::view::filter(predicate);
   auto result = numbers | ranges::view::filter(predicate) 
                         | ranges::view::transform([](int n) { return n * 2; });
   for (int n : evenNumbers)
   {
      std::cout << n << ' ';
   }
   std::cout << '\n';
      for (int n : result)
   {
      std::cout << n << ' ';
   }
}

evenNumbers is a range view adapter which sticks to the numbers range and changes the way it iterates.

result is a ranges of numbers that have been filtered on the predicate and then have been applied a funciton.

see the compile at compiler-explorer

credit: fluentcpp

Your question

Can we get an iterator that filters a vector from a predicate in C++?

in the sense you are asked it, can only be answered with: No. At the moment not (C++17). As per your requirement the iterator then would have to store the predicate and checking that for each modification of the position or for all dereferencing stuff. Ie before any dereferencing, the predicate would need to be checked. Because other code could modifiy your std::vector. The the iterator would need to check the predicate all the time. Also standard functionality like begin, end, distance would be rather complicated.

So you could create your own iterator by deriving from an existing iterator. Store the predicate and overload most of the functions to take care of the predicate. Very, very complicated, much work and maybe not, what you want to have. This would be the only way to get exact your requested functionality.

For work arounds, there are are many other possible solutions. Peolple will show you here.

But if I read your statement

"showing a view of the vector"

then life becomes easier. You can easily create a view of a vector by copying it conditionally with std::copy_if, as oblivion has written. That is in my opinion the best answer. It is none destructive. But it is a snapshot and not the original data. So, it is read only. And, it does not take into account changes to the original std::vector after the snapshot has been taken.

The second option, a combination of std::remove_if and std::erase, will destroy the original data. Or better said, it will invalidate the filtered out data. You could also std::copy_if the unwanted data to a backup area, std::remove_if them, and at the end add them again to the vector.

All these methods are critical, if the original data will be modified.

Maybe for you the standard std::copy_if is best to create a view. You would then return an iterator of copy and work with that.

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> testVector{ 1,2,3,4,5,6,7 };   // Test data
    std::vector<int> testVectorView{};              // The view

    // Create predicate
    auto predForEvenNumbers = [](const int& i) -> bool { return (i % 2 == 0);  };
    // And filter. Take a snapshot
    std::copy_if(testVector.begin(), testVector.end(), std::back_inserter(testVectorView), predForEvenNumbers);

    // Show example result
    std::vector<int>::iterator iter = testVectorView.begin();
    std::cout << *iter << '\n';

    return 0;
}

Please note. For big std::vectors, it will become a very expensive solution . . .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM