5

is there a smarter/nicer way to trim a vector given a value I want to erase, something similar to what trim does for strings, e.g. from

0 0 0 1 2 3 56 0 2 4 0 0

to

1 2 3 56 0 2 4

I cannot find a trim-like function, is find/erase the best option?

cpl
  • 669
  • 6
  • 23

4 Answers4

9

Suppose we are working with a

std::vector<int> v;

We want to trim zeros, so

auto const is_not_zero = [](int i){ return i != 0; };

To find the first non-zero element and delete everything before it,

auto const first_non_zero = std::find_if(v.begin(), v.end(), is_not_zero);
v.erase(b.begin(), first_non_zero);

To find the last non-zero, do the same with reverse iterators:

auto const last_non_zero = std::find_if(v.rbegin(), v.rend(), is_not_zero);
v.erase(last_non_zero.base(), v.end());

(std::reverse_iterator::base returns the underlying ordinary iterator from a reverse one).

lisyarus
  • 15,025
  • 3
  • 43
  • 68
4

In many cases, it's best to just not do anything with the vector and simply work with an iterator pair for the relevant range of elements. You could also use a span:

#include <gsl/span>

// ...

auto non_zeros = gsl::span(nonzeros_start_iter, nonzeros_end_iter);

and you can use this span like any standard library container. In C++20 you can switch gsl::span to std::span.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
3

This is a full implementation on any type with given parameter.

template<typename T>
void TrimVector(vector<T>& vec, const T& _t)
{
    auto end_index = find_if(vec.crbegin(), vec.crend(), [_t](const T& t) { return t != _t; } ).base();
    vec.erase(end_index, vec.cend());
    auto start_index = find_if(vec.cbegin(), vec.cend(), [_t](const T& t) { return t != _t; } );
    vec.erase(vec.cbegin(), start_index);
}

to use it,

//TrimVector<int>(MyVec, 0);
//changed as suggested below,
TrimVector(MyVec,0);

TEST:

int main()
{
    vector<int> myVec = { 0, 0, 0, 1, 2, 3, 56, 0, 2, 4, 0, 0 };

    TrimVector(myVec, 0);

    cout << "myVec:" << endl;
    for (auto it = myVec.begin(); it != myVec.end(); ++it)
        cout << "\t" << *it << endl;

    return 0;
}
Tony
  • 632
  • 1
  • 4
  • 18
  • Actually, there's no need to explicitly specify template parameter when using `TrimVector`. It's possible to write `TrimVector(myVec, 0)` and the compiler will deduce the parameter. – navyblue May 06 '19 at 19:59
  • The function template parameter deduction is nothing new. It's been there since 1998. Unless, you're taking about the 'pre-standard' version of C++ when it was something like a commercial product. – navyblue May 06 '19 at 20:44
0

You can simply find out the first non-zero element index from the beginning as well as from the end, and then slice the vector using those indexes.

Here is the example :

vector<int> vec = {0, 0, 0, 1, 2, 3, 56, 0, 2, 4, 0, 0};
    
// find begining index for non-zero element:
auto  beg_index = find_if_not(begin(vec), end(vec),[](int a){return a == 0;});
    
// find first non-zero element from the end :
auto end_index = find_if(vec.rbegin(), vec.rend(), [](int a){return a != 0;});
    
//slice the vector using found begining and end indexes :
    
vec = { beg_index, end_index.base()};

This will trip vector from both the ends.

Sadra
  • 2,480
  • 2
  • 20
  • 32
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 20 '22 at 08:18