Reputation: 45
First of all apologies if the title is a little ambiguous. I am new to C++ and am still trying to understand the intricacies of the language.
Basically I am working with a large list of words (a dictionary actually) which I have stored as in a vector.
My thought process on this is that I want to find all words with a 'c' in them. Each time there is a word with 'c' in it, look to see if it has a 'h' after it. If it does then throw back the word to be false. Hoping to get words like:
-carbon
-carry
etc etc
So far I have tried to implement some code and this is what I have so far:
bool hasCWithNoH(string wordName)
{
if (wordName.find('c'))
{
if (wordName.find('ch'))
{
return false;
}
}
}
which is called by this in the main{} source file:
void findCWithNoH()
{
cout << "List of words found with a 'c' but with no following 'h': " << endl;
for (Word word : words)
{
string testWord = word.getName();
if (word.hasCWithNoH(testWord))
{
cout << testWord << endl;
}
}
}
The results are like this:
czarowitz
czech
czechic
czechoslovakian
czechs
h
ha
haaf
haak
haar
habeas-corpus
habenaria
With the results finding every word beginning with 'c' and 'h' (not what I want).
I do not think I am using the find() function and the loops correctly, could I please have some advice on how to set the function out and whether the find() function is the correct function to use?
If more explanation is needed, please comment and I can elaborate further.
Upvotes: 1
Views: 159
Reputation: 91
There're two problems:
The correct function should be:
bool hasCWithNoH(string wordName) {
if (wordName.find("c") != string::npos) {
if (wordName.find("ch") != string::npos) {
return false;
} else {
return true;
}
} else {
return false;
}
}
EDIT: As the best answer suggests almost same code change while providing better explanation, I hereby update and provide something different.
We notice that the program called string::find twice - means each string is scanned twice. Can we save one pass? Of course.
Let's first setup the test program. We has test data and expected result. If the actual result is a bit different, the program will output the actual result and say "failed".
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
bool hasCWithNoH(const string& wordName) {
return true;
}
int main() {
string words[] = { // test data
"czarowitz",
"czech",
"czechic",
"czechoslovakian",
"czechs",
"h",
"ha",
"haaf",
"haak",
"haar",
"habeas - corpus",
"habenaria"
};
string expect_result[] = {
"czarowitz",
"habeas - corpus"
};
vector<string> actual_result;
for (string word : words) {
if (hasCWithNoH(word)) {
actual_result.push_back(word);
}
}
if (actual_result.size() == 2 &&
equal(expect_result, expect_result + 2, actual_result.begin())) {
cout << "Test passed" << endl;
} else {
cout << "Test failed, the following words are returned:" << endl;
for_each(actual_result.begin(), actual_result.end(), [](const string& e) { std::cout << e << endl; });
}
return 0;
}
Surely with the dummy function the test case fails.
If we change the function to the one in the answer above, the test will pass.
Now let's implement the one-pass function. From the description as well as the provided test data, we know the function should return true
only if the following are true:
So the function could be:
bool hasCWithNoH(const string& wordName) { // use reference type wherever possible
bool match = false; // By default, it doesn't match
for (string::const_iterator it = wordName.begin(); it != wordName.end(); ++it) {
if (*it == 'c') {
match = true; // If we see there's a 'c', it becomes a candidate...
if (it != (wordName.end() - 1) && *(it + 1) == 'h') {
match = false;
break; // But if later we see an 'h' right behind 'c', it's rejected immediately
}
}
}
return match;
}
Now the case passed again.
To improve, we notice the function accepts std::string only. Well it's better not to rely on certain data type. For example, if the caller has C-style string, we shouldn't force him to convert it to std::string before call the function. So we could use template function:
template <class Iterator>
bool hasCWithNoH(Iterator first, Iterator last) {
bool match = false;
for (Iterator it = first; it != last; ++it) {
if (*it == 'c') {
match = true;
if (it != last - 1 && *(it + 1) == 'h') {
match = false;
break;
}
}
}
return match;
}
Note that now it requires us to call the function in following way:
//...
for (string word : words) {
if (hasCWithNoH(word.begin(), word.end())) {
actual_result.push_back(word);
}
}
//...
Run the program and we see the test case passed again.
Upvotes: 2
Reputation: 1099
Small optimization: no need to do another find, just check the next char:
bool hasCWithNoH(const string & wordName)
{
size_t pos = wordName.find('c');
if ( pos == string::npos )
return false;
while ( pos !=string::npos )
{
if ( pos +1 < wordName.size() ) {
if ( wordName[pos+1] == 'h' )
return false;
}
else
return true;
pos = wordName.find('c', pos+1);
}
return true;
}
Upvotes: 0
Reputation: 1493
Note:
Assuming for all occurrences of 'c' there shouldn't be 'h' after it, this should work.
You don't return false when there is no c present Please check string::find for its return value
Return Value The position of the first character of the first match. If no matches were found, the function returns string::npos.
Change your function to
bool hasCWithNoH(string &wordName)
{
if (wordName.find('c')!=string::npos)
{
if (wordName.find("ch")!=string::npos)//Also use double quotes for string "ch"
{
return false;
}
else
{
return true;
}
}
else
{
return false; //Because word does not contain c.
}
}
Upvotes: 4
Reputation: 23884
assuming you need: Each time there is a word with 'c' in it, look to see if it has a 'h' after it. If it does then throw back the word to be false
Since you need c
exactly after h
you can use:
if( string[ string.find_first_of( 'c' ) + 1 ] == 'h' ){
return false;
} else {
return true;
}
NOTE:
return true
for a string if there is more than one c
even though other c
s have a h
. Like: "abc_ch"
Upvotes: 0