C++ - Vectors & Lists (filter, trim)
Overview
Estimated time: 50–70 minutes
Master the everyday containers: std::vector (dynamic array) and std::list (linked list). Practice multiple filtering and trimming techniques using both classic algorithms and modern ranges.
Learning Objectives
- Create, modify, and iterate std::vector and std::list.
- Filter elements using erase-remove, std::erase_if (C++20), and ranges::views::filter (C++20).
- Trim containers by predicate (remove leading/trailing elements) and trim strings (whitespace) within containers.
Prerequisites
std::vector basics (use by default)
#include <iostream>
#include <vector>
int main(){
std::vector v = {10, 20, 30};
v.push_back(40);
for (int x : v) std::cout << x << " ";
std::cout << "\n";
}
Expected Output: 10 20 30 40
Filter: erase-remove idiom (pre-C++20 and portable)
#include <vector>
#include <algorithm>
#include <iostream>
int main(){
std::vector v = {1,2,3,4,5,6};
// Remove even numbers
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; }), v.end());
for (int x : v) std::cout << x << ' ';
std::cout << "\n";
}
Expected Output: 1 3 5
Filter: std::erase_if (C++20)
#include <vector>
#include <algorithm> // std::erase_if
#include <iostream>
int main(){
std::vector names = {"", " Ann ", "Ben", " ", "Cat"};
std::erase_if(names, [](const std::string& s){ return s.empty(); });
for (auto& s : names) std::cout << '[' << s << "]\n";
}
Expected Output:
[]
[ Ann ]
[Ben]
[ ]
[Cat]
(empty entries removed)
Trim strings inside a vector
Implement a simple trim helper and apply it to each string. Then remove empty results.
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
static inline void trim_inplace(std::string& s){
auto not_space = [](unsigned char ch){ return !std::isspace(ch); };
// left trim
s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
// right trim
s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
}
int main(){
std::vector items = {" apple ", " ", " banana", "carrot "};
for (auto& s : items) trim_inplace(s);
// Drop empties
items.erase(std::remove_if(items.begin(), items.end(), [](const std::string& s){ return s.empty(); }), items.end());
for (auto& s : items) std::cout << s << "\n";
}
Expected Output:
apple
banana
carrot
Trim container by predicate (leading/trailing)
Example: trim leading/trailing zeros in a vector.
#include <vector>
#include <algorithm>
#include <iostream>
template <class T, class Pred>
void trim_container(std::vector& v, Pred is_trim){
// left trim
auto first = std::find_if_not(v.begin(), v.end(), is_trim);
// right trim
auto last = std::find_if_not(v.rbegin(), v.rend(), is_trim).base();
if (first < last) {
v.assign(first, last);
} else {
v.clear();
}
}
int main(){
std::vector v = {0,0,1,2,3,0,0};
trim_container(v, [](int x){ return x==0; });
for (int x : v) std::cout << x << ' ';
}
Expected Output: 1 2 3
Ranges view filter (C++20)
#include <vector>
#include <ranges>
#include <iostream>
int main(){
std::vector v = {1,2,3,4,5,6};
auto evens = v | std::views::filter([](int x){ return x%2==0; });
// Copy into a new vector if needed
std::vector out;
for (int x : evens) out.push_back(x);
for (int x : out) std::cout << x << ' ';
}
Expected Output: 2 4 6
std::list basics (when you need stable iterators, frequent splicing)
#include <list>
#include <iostream>
int main(){
std::list lst = {1,2,3};
lst.push_front(0);
for (int x : lst) std::cout << x << ' ';
}
Expected Output: 0 1 2 3
list filtering and trimming
#include <list>
#include <algorithm>
#include <iostream>
int main(){
std::list lst = {0,0,10,20,0,30,0};
// list::remove_if is member function
lst.remove_if([](int x){ return x==0; });
for (int x : lst) std::cout << x << ' ';
std::cout << "\n";
// Trim leading/trailing values by predicate
std::list lst2 = {0,0,1,2,3,0,0};
auto is_zero = [](int x){ return x==0; };
while (!lst2.empty() && is_zero(lst2.front())) lst2.pop_front();
while (!lst2.empty() && is_zero(lst2.back())) lst2.pop_back();
for (int x : lst2) std::cout << x << ' ';
}
Expected Output:
10 20 30
1 2 3
Beginner Boosters
#include
#include
#include
int main(){
std::vector v{5,4,3,2,1};
// Print with indices
for (std::size_t i=0;i=3 (erase-remove)
v.erase(std::remove_if(v.begin(), v.end(), [](int x){return x<3;}), v.end());
for (int x : v) std::cout << x << ' ';
}
Expected Output (example):
0:5 1:4 2:3 3:2 4:1
5 4 3
#include
#include
int main(){
std::list names = {" Ann ", " Bob", "", "Cat "};
// Drop empties
names.remove_if([](const std::string& s){ return s.empty(); });
for (auto& s : names) std::cout << '[' << s << "]\n";
}
Expected Output:
[ Ann ]
[ Bob]
[Cat ]
Common Pitfalls
- std::vector erase invalidates iterators and indices after the erased point. Recompute iterators if you continue iterating.
- For filtering, remember the erase-remove idiom; std::remove_if doesn’t shrink the container by itself.
- Prefer std::vector by default for cache locality and overall performance; std::list is for special cases (stable iterators, frequent splicing).
Checks for Understanding
- When do you prefer std::vector over std::list?
- What does the erase-remove idiom achieve?
- Show one way to trim leading/trailing elements by predicate.
Show answers
- Almost always; vector is contiguous and fast. Use list for stable iterators or splicing.
- It removes elements matching a predicate and then erases the "moved-to-end" range to shrink the vector.
- Find first/last non-matching iterators and assign the subrange; or pop_front/pop_back while predicate holds.
Exercises
- Write a function
filter_even(std::vector<int>)
that returns a new vector with only even numbers (use ranges if available, else erase-remove). - Implement
trim_by_pred(std::vector<T>&, Pred)
that trims both ends by predicate (as shown). - Given
std::vector<std::string>
, trim whitespace on each string and remove empties. Compare pre-C++20 and C++20 solutions.