C++ matching a specific string - c++

I have a function here that checks a text file for the existence of a string. The problem I have right now is that it's checking for substrings, so if the file has the word "IronMan" and any part of that string will pass the check.
Here's my code for the function:
void UIFunctions::checkIfExists()
{
while (!fin.eof())
{
getline(fin, line);
if (line.compare(getFriendName()) == string::npos)
{
setExists(false);
cout << getExistsErrorPrompt() << endl << endl;
if (listAfterCheck == 1)
{
listUsers();
}
cout << endl;
cout << endl;
cout << getErrorPrompt();
}
}
}
I searched for solutions and read that line.compare (changed from line.find) is what I need to match entire strings but instead of accepting substrings (like line.find did) now it doesn't accept anything at all to pass the check.
Edit: Also the file that this function uses would look something like this:
Ironman Captain Thor

The problem is that you are assuming that compare returns the same value as find would. The compare function returns an integer with value:
0 if the strings are equal
>0 if the compared string (in your case line) is lexicographically first
<0 if the comparing string (in your case getFriendName()) is lexicographically first
Therefore to check for an exact match, you would want:
if(line.compare(getFriendName()) == 0) {
setExists(false);
// ...
}
Comparing the result of compare with string::npos will never trigger since the magnitude of compare is the first differing character between your two strings.

std::basic_string::compare returns a value less than 0, 0 or more than 0. Since
if (line.compare(getFriendName()) == string::npos)
Is seeing if the return is the maximum value of the type of string::npos you more than likely will never trigger the if statement. If you only want to match a string exactly then you can use
if (line == getFriendName())
If instead you want to find any line that starts with getFriendName() then you want
if (line.find(getFriendName()) == 0)
In the above find will only return 0 if the string starts with what you want.

Related

Delete a line from a csv file c++

Here I have a file that reading in to a vector
typedef struct contacts
{
string name; //{jhonathan , anderson , felicia}
string nickName; //{jhonny , andy , felic}
string phoneNumber; // {13453514 ,148039 , 328490}
string carrier; // {atandt , coolmobiles , atandt }
string address; // {1bcd , gfhs ,jhtd }
} contactDetails;
vector <contactDetails> proContactFile;
I want to let user to delete a contact record from the file.For this I wrote a code.But from the code that i've written it deletes all the contact details from the file.But what I want here is when a user types a name then the program should delete only the name and the relevent nickname,carrier,phone number and address belongs to that name.Here is the code that i've written
string readString, selectContact;
cout << "Enter the name you want to delete" << endl;
cin >> selectContact;
ifstream fin;
fin.open(contactsFile);
if (!fin.is_open())
{
cout << "Unable to open Contacts.csv, please make sure file exists!" << endl;
}
ofstream fout;
fout.open("temp.csv" , ios::out);
while (getline(fin, readString))
{
if (((readString = readString.find(selectContact), 0)) == 0)
{
fout << readString <<',' << "\n";
}
cout << "Deleted Successfully" << endl;
showTableContacts();
}
if (((readString = readString.find(selectContact), 0) != 0))
{
cout << "\n" << selectContact << " not found" << endl;
}
fout.close();
fin.close();
remove("Contact.csv");//Deletes contacts.csv file
rename("temp.csv" , "Contact.csv");//Rename temp file as Contacts.csv
}
if (((readString = readString.find(selectContact), 0)) == 0)
This line has several issues.
readString.find(selectContact) returns the index of the match, or string::npos if it's not found. Checking that it is an exact match at position 0 is ok, technically, however, in your case if your string is "abcdefg" and you search for "abc" it will also match at position 0 when it shouldn't. You need to ensure the entire field matches, not just the first characters of it. (Find the first ',' in your line then ensure that all the bytes from the beginning up to the comma match your contact.)
Next consider this part:
readString = readString.find(selectContact)
You are assigning the result of the find to your string. That is, if find() returns 3, you will assign 3 to your string, which is interpreted as an ASCII character, and now your readString contains a single byte with junk in it. (In ASCII, 'A' is 65, etc)
So now your string is obliterated, and the index of your match is the contents, in the first byte (if the value is in range, or undefined behavior if it overflows.)
Code of this form:
if (xxx, 0)
is utilizing the comma operator, which sequences expressions by evaluating them from left to right, applying side effects (but throwing away the results) and the full expression evaluates to the value of the rightmost expression. Whatever xxx does (in your case, xxx is the code in step 2 above, obliterating your string) its return value is discarded and this whole expression evaluates to 0, which converts to a bool as false. Every time. (1,2,3) evaluates to 3, etc.
Therefore:
(readString = readString.find(selectContact), 0)
always evaluates to 0. And
if (((readString = readString.find(selectContact), 0)) == 0)
always obliterates your string, then tests if 0==0 which is always true.
Also, you almost never want to delete your input files. Rewriting them is dangerous enough, but you must be absolutely sure that everything succeeded before doing so; otherwise your program can end up causing a lot of pain if it deletes the input file and nothing else.

Cannot get ispunct or isspace to work, but isupper works fine. Can you help me?

This code only outputs the number of capital letters. It always outputs numMarks and numSpaces as 0. I've also tried sentence.c_str() with the same results. I cannot understand what's happening.
cout << "Please enter a sentence using grammatically correct formatting." << endl;
string sentence = GetLine();
int numSpaces = 0;
int numMarks = 0;
int numCaps = 0;
char words[sentence.length()];
for(int i = 0; i < sentence.length(); ++i)
{
words[i] = sentence[i];
if(isspace(words[i]) == true)
{
numSpaces++;
}
else if(ispunct(words[i]) == true)
{
numMarks++;
}
else if(isupper(words[i]) == true)
{
numCaps++;
}
}
cout << "\nNumber of spaces: " << numSpaces;
cout << "\nNumber of punctuation marks: " << numMarks;
cout << "\nNumber of capital letters: " << numCaps;
Edit: Fixed the problem. My compiler is weird. All I had to do was remove == true And it worked perfectly. Thanks for the information though. Now I know for the future
The functions isspace, ispunct, isupper that you are using have return type int. They return 0 if it is not a match, and non-zero if it is a match. They don't necessarily return 1, so testing == true may fail even though the check succeeded.
Change your code to be:
if ( isspace(words[i]) ) // no == true
and it should start working properly (so long as you don't type any extended characters - see below).
Further info: there are two different isupper functions in C++ (and the same for the other two functions). The are:
#include <cctype>
int isupper(int ch)
and
#include <locale>
template< class charT >
bool isupper( charT ch, const locale& loc );
You are currently using the first one, which is a legacy function coming from C. However you are using it incorrectly by passing a char; the argument must be in the range of unsigned char. Related question.
So to fix your code properly, choose one of the following two options (including the right header):
if ( isupper( static_cast<unsigned char>(words[i]) ) )
or
if ( isupper( words[i], locale() ) )
Other things: char words[sentence.length()]; is illegal in Standard C++; array dimensions must be known at compile-time. Your compiler is implementing an extension.
However this is redundant, you could just write sentence[i] and not use words at all.
Please change your code to
char c;
...
c = sentence[i];
if(isspace(c))
{
++numSpaces;
}
...
isspace returns zero if it is not a space or tab, but you can not assume that it is always returns 1 if it a space or tab. From http://www.cplusplus.com/reference/cctype/isspace/, it says, "A value different from zero (i.e., true) if indeed c is a white-space character. Zero (i.e., false) otherwise."
But if you test it with true, true is converted to 1 and the test fails because for example, on my machine, it returns 8 for a space.
Two things to consider.
First I would use " else if(ispunct(words[i]) !=0 )", instead of comparing the return of the function against true. Thissince the functions return an integer. The value of the integer returned might not match the case of being equal to whatever true is defined in your platform or compiler.
My second suggestion is to check your locale. In unix you can use the "locale" command. In windows you can ask google how to check your locale, for instance for windows 7.
https://www.java.com/en/download/help/locale.xml
If your locale is a "wide character" locale, you might need to use iswpunct (wint_t wc) instead of ispunct(int c).
I hope this helps

C++: string.find finds a char in a string only if its on pos1

I want to find a char (expected_char) in a string (word) by
if (word.find(expected_char)==true)
{
cout << "You got one! It's on pos" << word.find(expected_char);
}
else
{
...
}
If my string is e.g. "abcd" and i search for "c" else will be executed; if i search for "b" the if statement will be executed.
The return type of std::string::find() is the unsigned type std::string::size_type, and it returns either std::string::npos (which is the maximum value that std::string::size_type can represent) if the character was not found, or the first index of the found character in the string.
Now you are comparing the result of std::string::find() to the true, which results in integral promotion of the Boolean value true to the integral value 1. Thus, your condition is satisfied if and only if the character expected_char is found in position 1 (i.e. when it is the second character in the string).
If you want to check whether the character expected_char is in the string word, use
if (word.find(expected_char) != std::string::npos)
{
...
}
See this and you will understand. Interesting part:
std::string str("There are two needles in this haystack with needles.");
std::string str2("needle");
unsigned found = str.find(str2);
if (found != std::string::npos)
std::cout << "first 'needle' found at: " << found << '\n';
find returns a position, and the special value npos if there's no match. You need to test:
word.find(expected_char) != word.npos
(It so happens that b is in position 1, which is also the integral value of true.)

C++: Removing all asterisks from a string where the asterisks are NOT multiplication symbols

So basically, I might have some string that looks like: "hey this is a string * this string is awesome 97 * 3 = 27 * this string is cool".
However, this string might be huge. I'm trying to remove all the asterisks from the string, unless that asterisk appears to represent multiplication. Efficiency is somewhat important here, and I'm having trouble coming up with a good algorithm to remove all the non-multiplication asterisks from this.
In order to determine whether an asterisk is for multiplication, I can obviously just check whether it's sandwiched in between two numbers.
Thus, I was thinking I could do something like (pseudocode):
wasNumber = false
Loop through string
if number
set wasNumber = true
else
set wasNumber = false
if asterisk
if wasNumber
if the next word is a number
do nothing
else
remove asterisk
else
remove asterisk
However, that^ is ugly and inefficient on a huge string. Can you think of a better way to accomplish this in C++?
Also, how could I actually check whether a word is a number? It's allowed to be a decimal. I know there's a function to check if a character is a number...
Fully functioning code:
#include <iostream>
#include <string>
using namespace std;
string RemoveAllAstericks(string);
void RemoveSingleAsterick(string&, int);
bool IsDigit(char);
int main()
{
string myString = "hey this is a string * this string is awesome 97 * 3 = 27 * this string is cool";
string newString = RemoveAllAstericks(myString);
cout << "Original: " << myString << "\n";
cout << "Modified: " << newString << endl;
system("pause");
return 0;
}
string RemoveAllAstericks(string s)
{
int len = s.size();
int pos;
for(int i = 0; i < len; i++)
{
if(s[i] != '*')
continue;
pos = i - 1;
char cBefore = s[pos];
while(cBefore == ' ')
{
pos--;
cBefore = s[pos];
}
pos = i + 1;
char cAfter = s[pos];
while(cAfter == ' ')
{
pos++;
cAfter = s[pos];
}
if( IsDigit(cBefore) && IsDigit(cAfter) )
RemoveSingleAsterick(s, i);
}
return s;
}
void RemoveSingleAsterick(string& s, int i)
{
s[i] = ' '; // Replaces * with a space, but you can do whatever you want
}
bool IsDigit(char c)
{
return (c <= 57 && c >= 48);
}
Top level overview:
Code searches the string until it encounters an *. Then, it looks at the first non-whitespace character before AND after the *. If both characters are numeric, the code decides that this is a multiplication operation, and removes the asterick. Otherwise, it is ignored.
See the revision history of this post if you'd like other details.
Important Notes:
You should seriously consider adding boundary checks on the string (i.e. don't try to access an index that is less than 0 or greater than len
If you are worried about parentheses, then change the condition that checks for whitespaces to also check for parentheses.
Checking whether every single character is a number is a bad idea. At the very least, it will require two logical checks (see my IsDigit() function). (My code checks for '*', which is one logical operation.) However, some of the suggestions posted were very poorly thought out. Do not use regular expressions to check if a character is numeric.
Since you mentioned efficiency in your question, and I don't have sufficient rep points to comment on other answers:
A switch statement that checks for '0' '1' '2' ..., means that every character that is NOT a digit, must go through 10 logical operations. With all due respect, please, since chars map to ints, just check the boundaries (char <= '9' && char >= '0')
You can start by implementing the slow version, it could be much faster than you think. But let's say it's too slow. It then is an optimization problem. Where does the inefficiency lies?
"if number" is easy, you can use a regex or anything that stops when it finds something that is not a digit
"if the next word is a number" is just as easy to implement efficiently.
Now, it's the "remove asterisk" part that is an issue to you. The key point to notice here is that you don't need to duplicate the string: you can actually modify it in place since you are only removing elements.
Try to run through this visually before trying to implement it.
Keep two integers or iterators, the first one saying where you are currently reading your string, and the second one saying where you are currently writing your string. Since you only erase stuff, the read one will always be ahead of the writing one.
If you decide to keep the current string, you just need to advance each of your integers/iterators one by one, and copying accordingly. If you don't want to keep it, just advance the reading string! Then you only have to cut the string by the amount of asterisks you removed. The complexity is simply O(n), without any additional buffer used.
Also note that your algorithm would be simpler (but equivalent) if written like this:
wasNumber = false
Loop through string
if number
set wasNumber = true
else
set wasNumber = false
if asterisk and wasNumber and next word is a number
do nothing // using my algorithm, "do nothing" actually copies what you intend to keep
else
remove asterisk
I found your little problem interesting and I wrote (and tested) a small and simple function that would do just that on a std::string. Here u go:
// TestStringsCpp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std;
string& ClearAsterisk(string& iString)
{
bool bLastCharNumeric = false;
string lString = "0123456789";
for (string::iterator it = iString.begin(); it != iString.end() ; ++it) {
switch (*it) {
case ' ': break;//ignore whitespace characters
case '*':
if (bLastCharNumeric) {
//asterisk is preceded by numeric character. we have to check if
//the following non space character is numeric also
for (string::iterator it2 = it + 1; it2 != iString.end() ; ++it2) {
if (*it2 != ' ') {
if (*it2 <= '9' && *it2 >= '0') break;
else iString.erase(it);
break; //exit current for
}
}
}
else iString.erase(it);;
break;
default:
if (*it <= '9' && *it >= '0') bLastCharNumeric= true;
else bLastCharNumeric = false; //reset flag
}
}
return iString;
}
int _tmain(int argc, _TCHAR* argv[])
{
string testString = "hey this is a string * this string is awesome 97 * 3 = 27 * this string is cool";
cout<<ClearAsterisk(testString).c_str();
cin >> testString; //this is just for the app to pause a bit :)
return 0;
}
It will work perfectly with your sample string but it will fail if you have a text like this: "this is a happy 5 * 3day menu" because it checks only for the first nonspace character after the '*'. But frankly I can't immagine a lot of cases you would have this kind of construct in a sentence.
HTH,JP.
A regular expression wouldn't necessarily be any more efficient, but it would let you rely on somebody else to do your string parsing and manipulation.
Personally, if I were worried about efficiency, I would implement your pseudocode version while limiting needless memory allocations. I might even mmap the input file. I highly doubt that you'll get much faster than that.

How to compare a string with certain words and if a match is found print the whole string

I am trying to write a little program that will load in a file, compare each line with a specific array of words, and if that line has any of those words in it then I want to "print" that line out to a file.
My current code is:
int main()
{
string wordsToFind[13] =
{"MS SQL", "MySQL", "Virus", "spoof", "VNC", "Terminal", "imesh", "squid",
"SSH", "tivo", "udp idk", "Web access request dropped", "bounce"};
string firewallLogString = "";
ifstream firewallLog("C:\\firewalllogreview\\logfile.txt");
ofstream condensedFirewallLog("C:\\firewalllogreview\\firewallLog.txt");
if(firewallLog.fail())
{
cout << "The file does not exist. Please put the file at C:\\firewalllogreview and run this program again." << endl;
system("PAUSE");
return 0;
}
while(!firewallLog.eof())
{
getline(firewallLog, firewallLogString);
for(int i = 0; i < 13; i++)
{
if(firewallLogString == wordsToFind[i])
{
firewallLogString = firewallLogString + '\n';
condensedFirewallLog << firewallLogString;
cout << firewallLogString;
}
}
}
condensedFirewallLog.close();
firewallLog.close();
}
When I run the program it will compare the string, and if it matches it will only print out the specific word instead of the string. Any help would be much appreciated.
If I understand your problem correctly, you want to check if the line contains one of the word and print it if it does.
Right now what you are doing is this:
if(firewallLogString == wordsToFind[i])
Which checks if the string exactly matches the word. So, if the string contains one of the word but has other words in it, the test will fail.
Instead, check if the word is part of the string, like this:
if(firewallLogString.find(wordsToFind[i]) != string::npos)
There is something wrong in your code.
in this line
getline(firewallLog, firewallLogString);
you are reading a line, not a word, but then later you are comparing the whole line with a word from your array. Your IF shall not work actually.
Instead you need to use strstr method, to lookup for any word in your firewallLogString and if it finds you do the rest of your code.
Use std::string's find method to find the occurrence of your pattern words.