I have such function.
Input argument - vector of user names, vector of strings, number of top users.
First I count amount of occurancies for each user in strings. If there are several occurancies in one string - it still counts as 1.
Then I sort it by amount of occurancies. If amount of occurancies are equal - sort alphabetically user names.
And function return top N users with the most occurancy.
std::vector<std::string> GetTopUsers(const std::vector<std::string>& users,
const std::vector<std::string>& lines, const int topUsersNum) {
std::vector<std::pair<std::string, int>> userOccurancies;
//count user occurancies
for (const auto & user : users) {
int count = 0;
for (const auto &line : lines) {
std::regex rgx("\\b" + user + "\\b", std::regex::icase);
std::smatch match;
if (std::regex_search(line, match, rgx)) {
++count;
auto userIter = std::find_if(userOccurancies.begin(), userOccurancies.end(),
[&user](const std::pair<std::string, int>& element) { return element.first == user; });
if (userIter == userOccurancies.end()) {
userOccurancies.push_back(std::make_pair(user, count));
}
else {
userIter->second = count;
}
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
std::sort(userOccurancies.begin(), userOccurancies.end(),
[](const std::pair<std::string, int>& p1, const std::pair<std::string, int>& p2)
{ return (p1.second > p2.second) ? true : (p1.second == p2.second ? p1.first < p2.first : false); });
//extract top N users
int topUsersSz = (topUsersNum <= userOccurancies.size() ? topUsersNum : userOccurancies.size());
std::vector<std::string> topUsers(topUsersSz);
for (int i = 0; i < topUsersSz; i++) {
topUsers.push_back(userOccurancies[i].first);
}
return topUsers;
}
So for the input
std::vector<std::string> users = { "john", "atest", "qwe" };
std::vector<std::string> lines = { "atest john", "Qwe", "qwe1", "qwe," };
int topUsersNum = 4;
output will be qwe atest john
But it looks very complex. O(n^2) for loops + regex inside. It must be O(n^3) or even more.
Can you give me please advices how to make it with less complexity in c++11?
And also give me advices about code.
Or maybe there are better board for questions about complexity and performance?
Thank you.
UDP
std::vector<std::string> GetTopUsers2(const std::vector<std::string>& users,
const std::vector<std::string>& lines, const size_t topUsersNum) {
std::vector<std::pair<std::string, int>> userOccurancies(users.size());
auto userOcIt = userOccurancies.begin();
for (const auto & user : users) {
userOcIt->first = std::move(user);
userOcIt->second = 0;
userOcIt++;
}
//count user occurancies
for (auto &user: userOccurancies) {
int count = 0;
std::regex rgx("\\b" + user.first + "\\b", std::regex::icase);
std::smatch match;
for (const auto &line : lines) {
if (std::regex_search(line, match, rgx)) {
++count;
user.second = count;
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
std::sort(userOccurancies.begin(), userOccurancies.end(),
[](const std::pair<std::string, int>& p1, const std::pair<std::string, int>& p2)
{ return (p1.second > p2.second) ? true : (p1.second == p2.second ? p1.first < p2.first : false); });
//extract top N users
auto middle = userOccurancies.begin() + std::min(topUsersNum, userOccurancies.size());
int topUsersSz = (topUsersNum <= userOccurancies.size() ? topUsersNum : userOccurancies.size());
std::vector<std::string> topUsers(topUsersSz);
auto topIter = topUsers.begin();
for (auto iter = userOccurancies.begin(); iter != middle; iter++) {
*topIter = std::move(iter->first);
topIter++;
}
return topUsers;
}
Thanks to #Jarod42. I updated first part. I think that allocate memory to vector once at constructor is faster than call emplace_back every time, so I used it. If I am wrong - mark me.
Also I use c++11, not c++17.
time results:
Old: 3539400.00000 nanoseconds
New: 2674000.00000 nanoseconds
It is better but still looks complex, isn't it?
constructing regex is costly, and can be moved outside the loop:
also you might move string instead of copy.
You don't need to sort all range. std::partial_sort is enough.
And more important, you might avoid the inner find_if.
std::vector<std::string>
GetTopUsers(
std::vector<std::string> users,
const std::vector<std::string>& lines,
int topUsersNum)
{
std::vector<std::pair<std::string, std::size_t> userCount;
userCount.reserve(users.size());
for (auto& user : users) {
userCount.emplace_back(std::move(user), 0);
}
for (auto& [user, count] : userCount) {
std::regex rgx("\\b" + user + "\\b", std::regex::icase);
for (const auto &line : lines) {
std::smatch match;
if (std::regex_search(line, match, rgx)) {
++count;
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
auto middle = userCount.begin() + std::min(topUsersNum, userCount.size());
std::partial_sort(userCount.begin(),
middle,
userCount.end(),
[](const auto& lhs, const auto& rhs)
{
return std::tie(rhs.second, lhs.first) < std::tie(lhs.second, rhs.first);
});
//extract top N users
std::vector<std::string> topUsers;
topUsers.reserve(std::distance(userCount.begin(), middle));
for (auto it = userCount.begin(); it != middle; ++it) {
topUsers.push_back(std::move(it->first));
}
return topUsers;
}
i'm no professional coder, but i've made your code a bit faster (~90% faster, unless my math is wrong or i timed it wrong).
what it does is, it goes trough each of the lines, and for each line it counts the number of occurences for each user given. if the number of occurences for the current user are larger than the previous one, it moves the user at the beginning of the vector.
#include <iostream>
#include <Windows.h>
#include <vector>
#include <string>
#include <regex>
#include <algorithm>
#include <chrono>
std::vector<std::string> GetTopUsers(const std::vector<std::string>& users,
const std::vector<std::string>& lines, const int topUsersNum) {
std::vector<std::pair<std::string, int>> userOccurancies;
//count user occurancies
for (const auto & user : users) {
int count = 0;
for (const auto &line : lines) {
std::regex rgx("\\b" + user + "\\b", std::regex::icase);
std::smatch match;
if (std::regex_search(line, match, rgx)) {
++count;
auto userIter = std::find_if(userOccurancies.begin(), userOccurancies.end(),
[&user](const std::pair<std::string, int>& element) { return element.first == user; });
if (userIter == userOccurancies.end()) {
userOccurancies.push_back(std::make_pair(user, count));
}
else {
userIter->second = count;
}
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
std::sort(userOccurancies.begin(), userOccurancies.end(),
[](const std::pair<std::string, int>& p1, const std::pair<std::string, int>& p2)
{ return (p1.second > p2.second) ? true : (p1.second == p2.second ? p1.first < p2.first : false); });
//extract top N users
int topUsersSz = (topUsersNum <= userOccurancies.size() ? topUsersNum : userOccurancies.size());
std::vector<std::string> topUsers(topUsersSz);
for (int i = 0; i < topUsersSz; i++) {
topUsers.push_back(userOccurancies[i].first);
}
return topUsers;
}
unsigned int count_user_occurences(
std::string & line,
std::string & user
)
{
unsigned int occur = {};
std::string::size_type curr_index = {};
// while we can find the name of the user in the line, and we have not reached the end of the line
while((curr_index = line.find(user, curr_index)) != std::string::npos)
{
// increase the number of occurences
++occur;
// increase string index to skip the current user
curr_index += user.length();
}
// return the number of occurences
return occur;
}
std::vector<std::string> get_top_users(
std::vector<std::string> & user_list,
std::vector<std::string> & line_list
)
{
// create vector to hold results
std::vector<std::string> top_users = {};
// put all of the users inside the "top_users" vector
top_users = user_list;
// make sure none of the vectors are empty
if(false == user_list.empty()
&& false == line_list.empty())
{
// go trough each one of the lines
for(unsigned int i = {}; i < line_list.size(); ++i)
{
// holds the number of occurences for the previous user
unsigned int last_user_occur = {};
// go trough each one of the users (we copied the list into "top_users")
for(unsigned int j = {}; j < top_users.size(); ++j)
{
// get the number of the current user in the current line
unsigned int curr_user_occur = count_user_occurences(line_list.at(i), top_users.at(j));
// user temporary name holder
std::string temp_user = {};
// if the number of occurences of the current user is larger than the one of the previous user, move it at the top
if(curr_user_occur >= last_user_occur)
{
// save the current user's name
temp_user = top_users.at(j);
// erase the user from its current position
top_users.erase(top_users.begin() + j);
// move the user at the beginning of the vector
top_users.insert(top_users.begin(), temp_user);
}
// save the occurences of the current user to compare further users
last_user_occur = curr_user_occur;
}
}
}
// return the top user vector
return top_users;
}
int main()
{
std::vector<std::string> users = { "john", "atest", "qwe" };
std::vector<std::string> lines = { "atest john", "Qwe", "qwel", "qwe," };
// time the first function
auto start = std::chrono::high_resolution_clock::now();
std::vector<std::string> top_users = get_top_users(users, lines);
auto stop = std::chrono::high_resolution_clock::now();
// save the time in milliseconds
double time = std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count();
// print time
printf("%.05f nanoseconds\n", time);
// time the second function
auto start2 = std::chrono::high_resolution_clock::now();
std::vector<std::string> top_users2 = GetTopUsers(users, lines, 4);
auto stop2 = std::chrono::high_resolution_clock::now();
// save the time in milliseconds
double time2 = std::chrono::duration_cast<std::chrono::nanoseconds>(stop2 - start2).count();
// print time
printf("%.05f nanoseconds", time2);
getchar();
return 0;
}
results (for my PC at least, they're pretty consistent across multiple runs):
366800.00000 nanoseconds
4235900.00000 nanoseconds
Related
I'm trying to write a program that flips a region(represented as a set of Segments) with respect to a given row coordinate.
I don't know c++ well, so I got some errors I don't know why they appeared and how to fixed them.
Here is what I got so far:
struct Segment
{
int row;
int colStart;
int colStop;
};
std::vector<Segment> FlipVertically(const std::vector<Segment>& region, const int flipRow){
std::vector<Segment> vec, more, less;
for (std::vector<Segment>::reverse_iterator it = region.rbegin(); it != region.rend(); ++it){
if ((*it).row > flipRow){
more.insert(more.begin(), *it);
}
else{less.insert(less.begin(), *it);}
};
std::sort(more.begin(), more.end(), [](Segment const &a, Segment const &b){
return a.row > b.row;
});
std::sort(less.begin(), less.end(), [](Segment const &a, Segment const &b){
return a.row > b.row;
});
vec.insert(vec.end(), more.begin(), more.end());
vec.insert(vec.end(), less.begin(), less.end());
int counter = 1;
int i = 0;
while(i + 1 < vec.size()){
if (vec[i].row == vec[i + 1].row){
vec[i].row = counter;
}
else{
vec[i].row = counter;
counter++;}
i++;
}
vec.back().row = counter;
return vec;
}
The function should return Segments stored from top to bottom row, and in the same row from left to right column.
It says there is an error in while loop: comparison between signed and unsigned integer expressions [-Wsign-compare].
Also, I'm looking for tips to improve my algorithm because I feel it is not good because of two sorts I do after dividing data. I was thinking if it is possible to iterate through region and place Segments in the order I want during first iteration but wasn't able to find a way to do it.
This line
for (std::vector<Segment>::reverse_iterator it = region.rbegin(); it != region.rend(); ++it){
needs to be changed to
for (std::vector<Segment>::const_reverse_iterator it = region.rbegin(); it != region.rend(); ++it){
since region is a const container. Thus calling begin/rbegin/end/rend on const containers will return const_...iterator_type... for these.
But, better yet, use auto to simplify.
for(auto it = region.rbegin(); it != region.rend(); ++it)
But the whole first loop can be modified to go faster by looping forward
for(auto const& value : region)
{
if(value.row > flipRow)
{
more.push_back(value);
}
else
{
less.push_back(value);
}
}
On a final note. The first part of you algorithm
std::vector<Segment> vec, more, less;
for (std::vector<Segment>::reverse_iterator it = region.rbegin(); it != region.rend(); ++it){
if ((*it).row > flipRow){
more.insert(more.begin(), *it);
}
else{less.insert(less.begin(), *it);}
};
std::sort(more.begin(), more.end(), [](Segment const &a, Segment const &b){
return a.row > b.row;
});
std::sort(less.begin(), less.end(), [](Segment const &a, Segment const &b){
return a.row > b.row;
});
vec.insert(vec.end(), more.begin(), more.end());
vec.insert(vec.end(), less.begin(), less.end());
can be condensed down to
auto vec = region;
std::sort(vec.begin(), vec.end(), [](Segment const& a, Segment const& b) {
return a.row > b.row;
});
This rendering the use of flipRow obsolete. If you think it is needed
then there is likely another flaw in the implementation.
#include <bits/stdc++.h>
using namespace std;
#include <unordered_set>
#include <queue>
struct word {
string s;
int level;
word(string a, int b)
: s(a)
, level(b)
{
}
};
bool isadj(string s1, string s2)
{
int len = s1.length(), count = 0;
for (int i = 0; i < len; i++) {
if (s1[i] != s2[i])
count++;
if (count > 1)
return false;
}
return count == 1 ? true : false;
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList)
{
unordered_set<string> st;
for (string s : wordList)
st.insert(s); // adding elements into a set
if (st.find(endWord) == st.end())
return 0;
queue<word> q;
q.push(word(beginWord, 0)); // initialising the queue
while (!q.empty()) {
word temp = q.front(); // pop the current string
q.pop();
if (temp.s == endWord)
return temp.level;
for (auto it = st.begin(); it != st.end(); it++) { // loop over the set to find strings at a distance of 1 and add them to the queue
if (isadj(temp.s, *it)) // i have inserted code here to print the string *it
{
q.push(word(*it, temp.level + 1));
st.erase(*it); // delete the element to avoid looping
}
}
}
return 0;
}
int main()
{
// make dictionary
vector<string> D;
D.push_back("poon");
D.push_back("plee");
D.push_back("same");
D.push_back("poie");
D.push_back("plie");
D.push_back("poin");
D.push_back("plea");
string start = "toon";
string target = "plea";
cout << "Length of shortest chain is: "
<< ladderLength(start, target, D);
return 0;
}
The problem i am trying to solve is https://leetcode.com/problems/word-ladder/
I am unable to trace where I am using a memory that was deallocated again in my program?
The following are my attempts to debug :
I tried to run it on another online ide where the code compiles and runs successfully but gives a wrong answer . in order to debug it I have inserted some lines into my code in order to print all the strings which are at a distance of 1 for my current string. surprisingly an empty string is appearing to be in the set. Please help me in understanding where am I doing a mistake.
unordered_set::erase returns a value, and this returned value is important. You should not ignore it.
In your case, once you erase something from the set, it is invalid. Trying to increment it results in Undefined Behavior.
The correct approach is to replace the current iterator with the returned one, then not increment during the loop.
for (auto it = st.begin(); it != st.end(); )
if (...) {
// ...
it = st.erase(*it);
} else
++it;
After the line:
st.erase(*it); // delete the element to avoid looping
the it iterator is not valid and should not be used.
Your problem seems to be already addressed, but if you'd be interested, this'd also pass without using std::queue, only using std::unordered_set:
// The following block might slightly improve the execution time;
// Can be removed;
static const auto __optimize__ = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(NULL);
std::cout.tie(NULL);
return 0;
}();
// Most of headers are already included;
// Can be removed;
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_set>
#include <algorithm>
using ValueType = std::int_fast16_t;
static const struct Solution {
static const int ladderLength(
const std::string start,
const std::string end,
const std::vector<std::string>& words
) {
std::unordered_set<std::string> words_map(std::begin(words), std::end(words));
std::unordered_set<std::string> head;
std::unordered_set<std::string> tail;
std::unordered_set<std::string>* curr_head;
std::unordered_set<std::string>* curr_tail;
if (words_map.find(end) == std::end(words_map)) {
return 0;
}
head.insert(start);
tail.insert(end);
ValueType ladder = 2;
while (!head.empty() && !tail.empty()) {
if (head.size() < tail.size()) {
curr_head = &head;
curr_tail = &tail;
} else {
curr_head = &tail;
curr_tail = &head;
}
std::unordered_set<std::string> temp_word;
for (auto iter = curr_head->begin(); iter != curr_head->end(); iter++) {
std::string word = *iter;
for (ValueType index_i = 0; index_i < word.size(); index_i++) {
const char character = word[index_i];
for (ValueType index_j = 0; index_j < 26; index_j++) {
word[index_i] = 97 + index_j;
if (curr_tail->find(word) != curr_tail->end()) {
return ladder;
}
if (words_map.find(word) != std::end(words_map)) {
temp_word.insert(word);
words_map.erase(word);
}
}
word[index_i] = character;
}
}
ladder++;
curr_head->swap(temp_word);
}
return 0;
}
};
You might want to break it into more methods, a bit too long for a function.
References
For additional details, please see the Discussion Board where you can find plenty of well-explained accepted solutions with a variety of languages including low-complexity algorithms and asymptotic runtime/memory analysis1, 2.
I'm already familiar with the 1D bin packing nextFit, firstFit and bestFit + their offline algorithm variations. I mention these just for context.
My problem is about trying to find narrowest range (i,i+required_size) with width >= 1 that is not in the set of non-overlapping ranges:
int required_size;
std::set<std::pair<int,int>> ranges;
std::pair<int,int> result = findNarrowestFitFor(ranges,required_size);
How should I try solve this?
Explaining it to the duck: Obliviously I need to iterate the ranges and find two adjacent items that don't overlap.
Beta asked for example code, so here is what I'm at:
std::vector<char> buffer;
std::map<int,int> ranges;
// Search for free space by finding narrowest range
// that is not in ranges
const char * find_free_area(size_t nbytes) {
ptrdiff_t fit = buffer.capacity();
auto pos = ranges.begin();
auto itr = pos;
if(ranges.empty()) {
return buffer.data();
}
while(itr != ranges.end()) {
// Find next hole begin
itr = std::adjacent_find(ritr, ranges.end(),
[]( const std::pair<const int,int> & a,
const std::pair<const int,int> & b) {
return a.second != b.first;
});
if(itr == ranges.end()) {
itr = ranges.rbegin().base();
}
// Get next range
auto next = std::next(itr);
if(next != ranges.end()) {
auto space = next->first - itr->second;
if(space < fit) {
fit = space;
pos = ranges.begin();
std::advance(pos, itr->second);
}
} else if(itr->second ) {
// todo..
}
}
}
This question already has answers here:
Frequency of ngrams (strings) in tokenized text
(2 answers)
Closed 3 years ago.
How can I use this for x amounts of numbers ? for my approach, its hard coded to 2 substrings. Also is there a better way with less time complexity? There may be a loop hole over here which needs to be fixed about the number of num im passing as I am not using the um parameter at all.
Your current approach has a few problems, including a hard-coded maximum number of ngrams, and the fixed ngram size. In addition, your short variable names and lack of comments do not help explain the code to whoever is reading it.
A simpler solution is to use a map to count the number of times each ngram occurs, and then find the one with the highest count. That would give rougly N.logN time complexity. Alternatively unordered_map would be closer to linear time complexity.
There will of course be an edge case where more than one ngram occurs the same highest count. You would need to decide which of a variety of strategies should be used to resolve that. In my example, I take advantage of intrinsic ordering of std::map to select the ngram with the lowest sort order. If using unordered_map, you'd need a different strategy for resolving contention in a deterministic way.
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
std::string ngram(const std::string &input, int num)
{
if (num <= 0 || num > input.size()) return "";
// Count ngrams of size 'num'
std::map<std::string, int> ngram_count;
for(size_t i = 0; i <= input.size() - num; i++)
{
++ngram_count[input.substr(i, num)];
}
// Select ngram with highest count
std::map<std::string, int>::iterator highest = std::max_element(
ngram_count.begin(), ngram_count.end(),
[](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b)
{
return a.second < b.second;
});
// Return ngram with highest count, otherwise empty string
return highest != ngram_count.end() ? highest->first : "";
}
int main()
{
std::cout << ngram("engineering", 2) << std::endl;
std::cout << ngram("engineering", 3) << std::endl;
return 0;
}
I did it a bit different than paddy, so I thought I would post it. Use std::set. He explains the issue so should get the credit for your answer.
struct test {
test(const std::string& str) :val(str), cnt(0) {}
test(const test& thet) { *this = thet; }
std::string val;
int cnt;
friend bool operator < (const test& a, const test& b) { return a.val < b.val; }
};
using test_set_type = std::set<test>;
const test ngram(std::string A, int num) {
test_set_type set;
for (auto it = A.begin(); it < A.end() - num + 1; ++it)
{
auto found = set.find(std::string(it, it + num));
if (found != set.end())
++const_cast<test&>(*found).cnt;
else
set.insert(std::string(it, it + num));
}
int find = -1;
test_set_type::iterator high = set.begin();
for (auto it = set.begin(); it != set.end(); ++it)
if(it->cnt > find)
++find, high= it;
return *high;
}
int main() {
int num = 2;
std::string word("engineering");
std::cout << ngram(word, num).val << std::endl;
return 0;
}
i have given a vector `
vector<string> inputArray = { "aba","aa","ad","vcd","aba" };
and i want to return this vector which contains only string with the longest length, in this case i want to return only {"aba","vcd","aba"}, so for now i want to erase elements which length is not equal to the highest `
vector<string> allLongestStrings(vector<string> inputArray) {
int length = inputArray.size();
int longstring = inputArray[0].length();
int count = 0;
vector<string> result;
for (int i = 0; i < length; i++)
{
if (longstring < inputArray[i].length())
{
longstring = inputArray[i].length();
}
count++;
}
for (int = 0; i<count;i++)
{
if (inputArray[i].length() != longstring)
{
inputArray[i].erase(inputArray.begin() + i);
count--;
i--;
}
}
return inputArray;
}
but i get this error no instance of overloaded fucntion "std::basic_string<_Elem,_Traits,_Alloc>::erase[with_Elem=char,_Traits=std::char_traits<char>,_Alloc=std::allocator<char>]" matches the argument list" in inputArray[i].erase(inputArray.begin()+i); this line
what's wrong?
There are other problems, but this specific compiler message is telling you that's not the right way to remove specific character(s) from a string.
However, reading the question in the OP, we see that you wanted to remove a string from a vector. To fix that one specific error, simply change
inputArray[i].erase( /*character position(s) in the string*/ )
to
inputArray.erase( /*some position in the array*/ )
Or you could fix it so it uses an iterator in the string denoted by inputArray[i] to actually delete characters from that string, which of course isn't what you said you wanted to do. The point is, the error message is because you're using the wrong iterator type because you think that you're working with a vector, but you actually told it to work with a string that you got out of the vector.
And then you will compile and have other issues which are well covered in comments already.
The issue with inputArray[i].erase(inputArray.begin() + i); can be fixed as shown in Kenny Ostrom's answer.
I'd like to point out that the OP could make use of the erase-remove idiom or even create a new vector with only the bigger strings instead (the posted code is already copying the source vector).
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
template <typename InputIt>
auto only_the_longest_of(InputIt first, InputIt last)
{
using value_type = typename std::iterator_traits<InputIt>::value_type;
std::vector<value_type> result;
// find the longest size
auto longest = std::max_element(first, last,
[](value_type const &a, value_type const &b) {
return a.size() < b.size();
});
if ( longest == last )
return result;
// extract only the longest ones, instead of erasing
std::copy_if( first, last, std::back_inserter(result)
, [max_size = longest->size()] (value_type const& v) {
return v.size() >= max_size;
});
return result;
}
template <typename T>
auto erase_the_shortest_from(std::vector<T> &input)
{
// find the longest size
auto longest = std::max_element(input.cbegin(), input.cend(),
[](T const &a, T const &b) {
return a.size() < b.size();
});
if ( longest == input.cend() || longest->size() == 0 )
return input.end();
// implement erase-remove idiom
return input.erase(std::remove_if(
input.begin(), input.end(), [max_size = longest->size()] (T const &v) {
return v.size() < max_size;
}));
}
int main()
{
std::vector<std::string> test = {
"aba", "aa", "ad", "vcd", "aba"
};
// The original vector remain unchanged
auto result = only_the_longest_of(test.cbegin(), test.cend());
for (auto const& str : result)
std::cout << str << '\n';
std::cout << '\n';
// This will change the vector
erase_the_shortest_from(test);
for (auto const& str : test)
std::cout << str << '\n';
}