Delete elements in a std::vector<std::string> which matches with the characters in another given std::string

Viewed 217

How can I remove elements in the vector alphabets which matches with any of the characters in the string plaintext?

Here's my attempt:

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

int main()
{
   //init
   std::vector<std::string> alphabets{ "a", "b", "c", "d", "e", "f", "g", "h", "i", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
   //input
   std::string plaintext;
   std::cout << "enter plain text: ";
   std::cin >> plaintext;

   for (std::string::iterator it = plaintext.begin(); it != plaintext.end(); it++)
   {
      std::vector<std::string>::iterator toErase;
      toErase = std::find(alphabets.begin(), alphabets.end(), *it);
      if (toErase != alphabets.end())
      {
         alphabets.erase(toErase);
      }
   }
}

When I tried to compile it. But I got this error:

17: note:   'std::__cxx11::basic_string<char>' is not derived from
'const std::istreambuf_iterator<_CharT, _Traits>' { return *__it == _M_value; }
...

203 : 5 : note : candidate : 'template<class _CharT, class _Traits> bool 
std::operator==(const std::istreambuf_iterator<_CharT, _Traits>&, const
   std::istreambuf_iterator<_CharT, _Traits>&)'
   operator==(const istreambuf_iterator<_CharT, _Traits> & __a,
      ...
4 Answers

The *it has a type of char not std::string. This is what the compiler complaining about. Therefore you need to pass a std::string to the std::find as follows.

auto toErase = std::find(alphabets.begin(), alphabets.end(), std::string{ *it });
 //                                                          ^^^^^^^^^^^^^^^^^^

Here is a demo.


Also, note the followings:

  • You can change the std::vector<std::string> alphabets to a std::vector<char> alphabets or even a single std::string as your alphabets containes/ represents chars as strings. In the case of std::strings (i.e. alphabets), the std::basic_string::find is more appropriate to use, rather than having more general std::find at first place.
  • For vector erase, you could use erase–remove idiom, or since C++20, using non-member function of std::vector itself, so called std::erase_if.

In

std::find(alphabets.begin(), alphabets.end(), *it);

alphabets.begin() and alphabets.end() are std::vector of std::strings iterators, but it is a std::string iterator, it iterates over characters, these arguments are incompatible, the can't be used together in std::find without some kind of conversion.

That said a better way to correct your code would be to turn your alphabets container, which is overly complicated, from std::vector<std::string> into a simple std::string.

Live demo

//init
std::string alphabets{"abcdefghiklmnopqrstuvwxyz"}; //<--- simple string

//input
std::string plaintext;
std::cout << "enter plain text: ";
std::cin >> plaintext;

for (std::string::iterator it = plaintext.begin(); it != plaintext.end(); it++)
{
    std::string::iterator toErase; //<--- string iterator
    /*or auto */ toErase = std::find(alphabets.begin(), alphabets.end(), *it);
    if (toErase != alphabets.end())
    {
        alphabets.erase(toErase);
    }
}

This is what I understood:
You have a string say str1. Now you've been given another string say, str2. Now you want to delete all characters from str1, which are present in str2.

In that case, I suggest scanning the input as a string instead of vector, and then using the delete loop

for(auto it = str1.begin(); it != str1.end(); it++) {
    if(str2.find(std::string{*it}) != std::string::npos) {
        it = str1.erase(it);
    }
}

A C++20 oneliner solution.

It focuses more on what you want to do (it uses std::erase_if because we want to erase elements based on a condition, and it uses std::find to check that condition), rather than how you do it (there are no for loops indeed.

#include <string>
#include <vector>
#include <iostream>

int main() {

    // inputs
    // I don't like this alphabet; a std::vector<char> would have been much better
    // and it would allow to remove the ugly [0] in the std::find below
    std::vector<std::string> alphabet{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};
    std::string plaintext{"abcdefghijklmnopq"};


    // the solution is a oneliner
    std::erase_if(alphabet, [&plaintext](auto x){
        return !(std::find(std::begin(plaintext), std::end(plaintext), x[0]) == std::end(plaintext));
    });

    // output to verify the correct result
    for (auto& e : alphabet) { // prints r s t u v w x y z
        std::cout << e << ' ';
    }
}
Related