Is there an efficient way of executing a function if any string inside a vector contains a substring?
Something along these lines
if(vector.contains(strstr(currentVectorElement,"substring"))) {
//do something
}
if(vector.contains(strstr(currentVectorElement,"substring2"))) {
//do something else
}
The only thing I can think of is iterating over each string and check if the substring exists.
You can use std::find_if with a lambda expression
if(std::find_if(vec.begin(), vec.end(), [](const std::string& str) { return str.find("substring") != std::string::npos; }) != vec.end()) {
...
}
What you need is std::search()
http://en.cppreference.com/w/cpp/algorithm/search
Related
I have some parsed text from the Vision API, and I'm filtering it using keywords, like so:
if (finalTextRaw.find("File") != finalTextRaw.npos)
{
LogMsg("Found Menubar");
}
E.g., if the keyword "File" is found anywhere within the string finalTextRaw, then the function is interrupted and a log message is printed.
This method is very reliable. But I've inefficiently just made a bunch of if-else-if statements in this fashion, and as I'm finding more words that need filtering, I'd rather be a little more efficient. Instead, I'm now getting a string from a config file, and then parsing that string into an array:
string filterWords = GetApp()->GetFilter();
std::replace(filterWords.begin(), filterWords.end(), ',', ' '); ///replace ',' with ' '
vector<int> array;
stringstream ss(filterWords);
int temp;
while (ss >> temp)
array.push_back(temp); ///create an array of filtered words
And I'd like to have just one if statement for checking that string against the array, instead of many of them for checking the string against each keyword I'm having to manually specify in the code. Something like this:
if (finalTextRaw.find(array) != finalTextRaw.npos)
{
LogMsg("Found filtered word");
}
Of course, that syntax doesn't work, and it's surely more complicated than that, but hopefully you get the idea: if any words from my array appear anywhere in that string, that string should be ignored and a log message printed instead.
Any ideas how I might fashion such a function? I'm guessing it's going to necessitate some kind of loop.
Borrowing from Thomas's answer, a ranged for loop offers a neat solution:
for (const auto &word : words)
{
if (finalTextRaw.find(word) != std::string::npos)
{
// word is found.
// do stuff here or call a function.
break; // stop the loop.
}
}
As pointed out by Thomas, the most efficient way is to split both texts into a list of words. Then use std::set_intersection to find occurrences in both lists. You can use std::vector as long it is sorted. You end up with O(n*log(n)) (with n = max words), rather than O(n*m).
Split sentences to words:
auto split(std::string_view sentence) {
auto result = std::vector<std::string>{};
auto stream = std::istringstream{sentence.data()};
std::copy(std::istream_iterator<std::string>(stream),
std::istream_iterator<std::string>(), std::back_inserter(result));
return result;
}
Find words existing in both lists. This only works for sorted lists (like sets or manually sorted vectors).
auto intersect(std::vector<std::string> a, std::vector<std::string> b) {
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
auto result = std::vector<std::string>{};
std::set_intersection(std::move_iterator{a.begin()},
std::move_iterator{a.end()},
b.cbegin(), b.cend(),
std::back_inserter(result));
return result;
}
Example of how to use.
int main() {
const auto result = intersect(split("hello my name is mister raw"),
split("this is the final raw text"));
for (const auto& word: result) {
// do something with word
}
}
Note that this makes sense when working with large or undefined number of words. If you know the limits, you might want to use easier solutions (provided by other answers).
You could use a fundamental, brute force, loop:
unsigned int quantity_words = array.size();
for (unsigned int i = 0; i < quantity_words; ++i)
{
std::string word = array[i];
if (finalTextRaw.find(word) != std::string::npos)
{
// word is found.
// do stuff here or call a function.
break; // stop the loop.
}
}
The above loop takes each word in the array and searches the finalTextRaw for the word.
There are better methods using some std algorithms. I'll leave that for other answers.
Edit 1: maps and association
The above code is bothering me because there are too many passes through the finalTextRaw string.
Here's another idea:
Create a std::set using the words in finalTextRaw.
For each word in your array, check for existence in the set.
This reduces the quantity of searches (it's like searching a tree).
You should also investigate creating a set of the words in array and finding the intersection between the two sets.
for (const auto & rRec : m_map_handshake)
{
if (rRec.second->GetHostName() == inet_ntoa(c_rSockAddr.sin_addr))
{
return true;
}
}
I have code like this, but range-based for loop won't work on old gcc compiler.
Is there any way to work aroung this? I am not an expert on C++
You can use a normal for loop. Looks like it's std::map. Use iterator to traverse the elements and match the condition.
for(const <map-type>::iterator it = m_map_handshake.begin(); it != m_map_handshake.end();++it){
if (it->second->GetHostName() == inet_ntoa(c_rSockAddr.sin_addr))
{
return true;
}
}
Here <map-type> will be the type of m_map_handshake.
I am trying to find if a string words contains any instruction form machine_opTable, a vector of pair-string,int.
What should my lambda function look like in find_if ?
If you have any other approach, please let me know.
As of now, my code looks like ...
# define RX 4
# define RR 2
...
vector < pair <string, int> > machine_opTable = { {"L", RX},
{ "A", RX},
{"ST", RX}
};
words = " L 1, SMB1";
string inMachineop;
for ( auto instruction: words){
inMachineop = find_if ( begin(machine_opTable), end(machine_opTable), [] (const pair<string,int>& p) { return ( p.first == instruction ? p.first : "NOTFOUND"); });
}
I would love to return iterator pointing to that pair... Please show me how it's done.
Thank you.
Return value from find_if according to reference is
An iterator to the first element in the range for which pred does not return false.
pred is your lambda and it must return bool value.
If you want to use instruction in your lambda, you have to capture this variable
[instruction] (const pair<string,int>& p) { return p.first == instruction; }
and the call of find_if when we know it returns iterator looks as follows
auto it = find_if ( begin(machine_opTable), end(machine_opTable), [instruction] (const pair<string,int>& p) { return p.first == instruction; });
if (it != machine_opTable.end())
{
// access to found pair
}
else
; // pair not found
I see a bug here:
for ( auto instruction: words){
You iterate by characters, not by words here. You need to split by space first. Try to use this code for that task https://stackoverflow.com/a/27511119/9187525
Bugs related to find_if() was explained by others :)
struct ABC
{
int a;
string b;
};
I have a vector of objects to the above struct. And want to search the vector based on variable "b"?
I have logic as below.
vector<ABC> vec = ...;//vec has my objects
for(vector<ABC>::iterator it = vec.begin();
it != vec.end();
++it)
{
if(search_str == (it->b))//search string is my string which i need to search
{
}
}
I have extensively tested the above code and it works. I want to know if there is a better way to achieve this. Maybe using find().
Simple, readable, lifted from Sam's comment:
auto found = std::find_if(vec.begin(), vec.end(), [&](auto const &e) {
return e.b == search_str;
});
And now found is an iterator to the first matching element, or vec.end() if none was found.
You can also use range based for loops in some cases, give you much clearer code.
for (auto const &p : vec)
{
if (p == search_str)
{
//--- Handle the find ---//
//if you want to stop...
break;
}
}
One of the better method to compare two strings is using compare method in C++.
Suppose you want to compare two strings S1 and S2. You can use equality operator( == ) as you have already used.
But using std::string::compare() function has it's own benefit.
We can not only compare two strings but can also check if one is less or greater.
std::string::compare() function return an int:
zero if S1 is equal to S2.
less than zero if S1 is less than S2.
greater than zero if S1 is greater than S2.
So your code can be formatted as:
vector<ABC> vec = ...;//vec has my objects
for(vector<ABC>::iterator it = vec.begin(); it != vec.end(); ++it){
if(!search_str.compare(it->b))
{
//match found
}
}
Context:
I perform a std::find with a std::string on a <-string,vector->map. It then returns me an iterator of vectors, I keep the returned iterator in a const-iterator.
Problem:
I now want to iterate through the returned const-iterator, and string compare every vector at index 0. so something like:
while (iterator != map.end())
if ( myStr == iterator.at(0) )
break;
else
iterator++
That approach works just fine for me, I was wondering if there is a more elegant way of doing this, am I missing something?
Thanks for your help with this =]
Instead of explicitly coding the search you could use std::find_if():
std::vector<std::vector<std::string>> vstring
{
{ "no", "yes" },
{ "help", "yes" },
{ "true", "false" }
};
const std::string myStr = "help";
auto f = std::find_if(vstring.begin(), vstring.end(),
[&](std::vector<std::string>const & vs)
{
return !vs.empty() && myStr == vs[0];
});
if (f != vstring.end())
{
// Found.
}
See demo at http://ideone.com/nkI7fk .
One way to make this more "elegant" would be something like this:
// C++11 allows `using` to be used instead of `typedef`
using map_type = std::map<std::string, std::vector<some_type>>;
// First find the starting point of our secondary search
const auto itr = map.find(some_string);
// Do secondary search
const auto found = std::find_if(itr, map.end(),
[](const map_type::value_type& pair)
{
return (!pair.second.empty() &&
pair.second[0] == myStr);
});
if (found != map.end())
{
// Found the item
}
There is a very poor way I can imagine. It's not ordinary and should(or even must) never be used. Overloade comparison operator for vector, so it would compare only 0 positions. And then use map::find() method. Just fun.