Removing Empty Elements from a Vector of Strings
Asked Answered
C

4

17

I'm attempting to make a small program that processes INI files, for use in a later project, first by reducing its size once loaded into memory. Thus,

where vLine is a vector containing the file contents

for (unsigned int i = 0; i < vLine.size(); i++)
{
   if (!vLine[i].find(';', 0))
   {
       vLine[i].erase();
   }
}

Upon printing vLine, I'll be left with spaces where once a line beginning with a semi-colon existed, such as

1.    
2. property
3. property
4. 
5. property

Using resize() appears to remove the last element from the list rather than remove these empty portions. The same problem exists where I remove lines that only contain whitespace with erase().

Is it possible to remove these empty elements while preserving the order of vLine?

(Apologies for not using iterators in this.)

Complementary answered 7/10, 2011 at 2:19 Comment(3)
Re: (Apologies for not using iterators in this.) - Why not use iterators?Linked
Lack of knowledge regarding their proper use. It seems (seemed?) possible to do this without them.Complementary
You can remove the lines from the vector rather than just erasing the string's contents (which is what vLine[i].erase() does, no? Call vLine.erase() since that erases from the vector. Then rewrite the file out. An idiomatic way to do this is C++'s erase-remove idiom, though you would want to use std::remove_if from <algorithm> to use a conditional.Fullblooded
L
16

This:

vLine[i].erase(); 

does not erase vLine[i] from the vector. The expression vLine[i] returns a reference to the element at index i. So assuming that vLine is of type std::vector<std::string>, the function call erase() actually calls string::erase() on the element, not vector::erase() on the vector. All you're doing is making that particular element blank.

What you probably want is something like this:

vLine.erase(vLine.begin() + i);

This actually removes the element from the vector. Now, this does invalidate all current iterators to the vector, and the indices won't be right anymore. This is a situation where you really need to use iterators.

std::vector<std::string>::iterator i = vLine.begin();
while(i != vLine.end())
{
    if(i->find(';', 0) != std::string::npos)
    {
        i = vLine.erase(i);
    }
    else
    {
        ++i;
    }
}

But there's an even easier way to do this: use the standard algorithm std::remove_if() with a functor then call vLine.erase().

struct HasSemicolon
{
    bool operator()(const std::string& s)
    {
        return s.find(';', 0) != std::string::npos;
    }
};

// ...

vLine.erase(std::remove_if(vLine.begin(), vLine.end(), HasSemicolon()), vLine.end());

If you can use a C++11 compiler, then you can also use lambda expressions to be even more concise.

Linked answered 7/10, 2011 at 2:24 Comment(3)
Much obliged, In Silico. I'll take the time to learn iterators and alter my application accordingly.Complementary
Could you add an example using C++11 lambdas? I've been trying to figure it out for the better part of an hour now but I'm horrible at reading C++ docs.Titillate
@QPaysTaxes en.cppreference.com/w/cpp/algorithm/remove has an example with a lambda.Fibroma
S
11

The problem is in your logic for removing the elements. When you come across an element at index i that you want to erase, you clear its value, but you do not remove it from the vector.

The standard and simple way to do what you want to do is std::remove_if:

vLine.erase(
    std::remove_if(
        vLine.begin(),
        vLine.end(),
        [](std::string const& s) { return s.size() != 0 && s.front() == ';'; }),
    vLine.end());
Substantialize answered 7/10, 2011 at 2:35 Comment(0)
M
8

Use the erase/remove-idiom, preferably with a lambda from C++11:

foo.erase(std::remove_if(foo.begin(), foo.end(), 
                         [](const std::string& s) 
                         { return s.find(';', 0); }));
Malley answered 7/10, 2011 at 2:33 Comment(1)
I think { return s.find(';', 0); })); needs to be: { return s.find(';', 0); }), foo.end()); Otherwise the space does not appear to be deallocated from foo. At least this is the result I got. @Mankarse's answer (basically the same) seemed to do the job! : )Scarcely
W
2

With C++20 you can use std::erase which is equivalent to the erase-remove idiom.

std::erase_if(vLine, [](auto&& str){
    return str.find(';', 0) != std::string::npos;
})
Windshield answered 1/7, 2022 at 7:55 Comment(1)
This answer was a total win for me today.Ionogen

© 2022 - 2024 — McMap. All rights reserved.