Suggestions that could improve my string splitting function - c++

I'm new to C++ and I'm trying to write some basic functions to get the hang of some of it, I decided on making a custom function to split up a string into tokens every time a specific delimiter is reached.
I've made it work successfully, but since I'm new, I'd like to hear from more experienced programmers on if there is a better way to go about it. This is my code:
vector<string> split(string const str, string const separator=" ") {
int str_len = str.length();
int sep_len = separator.length();
int current_index {0};
vector<string> strings {};
for(int i {0}; i < str_len; ++i) {
if(str.substr(i, sep_len) == separator) {
strings.push_back(str.substr(current_index, i-current_index));
current_index = i + sep_len;
}
}
strings.push_back(str.substr(current_index, str_len-current_index));
return strings;
}
One thing I will say is, I don't like how I had to put
strings.push_back(str.substr(current_index, str_len-current_index));
this after the entire iteration to get the final part of the string. I just can't think of any different methods.

Use std::string::find() to find separators in the string, which is probably much more efficient than your loop that checks for each possible position if the substring at that position matches the separator. Once you have that, you can make use of the fact that if the separator is not found, find() returns std::string::npos, which is the largest possible value of std::string::size_type, so just pass this to substr() to get everything from the current position to the end of the string. This way you can avoid the second push_back().
vector<string> split(string const &str, string const &separator=" ") {
string::size_type current_index {};
vector<string> strings;
while (true) {
auto separator_index = str.find(separator, current_index);
strings.push_back(str.substr(current_index, separator_index - current_index));
if (separator_index == str.npos)
break;
else
current_index = separator_index + separator.size();
}
return strings;
}
Note: ensure you pass the input parameters by reference to avoid unnecessary copies being made.

Related

How to find certain substring in string and then go back to certain character?

I save messages in string and I need to make filter function that finds user specified word in those messages. I've split each message by '\n' so the example of one chat would be:
user1:Hey, man\nuser2:Hey\nuser1:What's up?\nuser2:Nothing, wbu?\n etc.
Now user could ask to search for word up and I've implemented a search like this:
for (auto it = msg.cbegin(); (it = std::find(it, msg.cend(), str)) != msg.cend(); it++)
and I could put that string into stringstream and use getline to \n, but how do I go backwards to previous \n so I can get full message? Also, what about first message, cause it doesn't start with \n?
Since you said you split the strings, I image you have a vector of strings where you want to find up for example. You would do something like this
for (const auto& my_string: vector_of_strings){
if (my_string.find("up") != string::npos) {
// message containing up is my_string
}
}
In case you haven't split the strings in a vector you can use this func inspired by this:
vector<string> split(const string& s, const string& delimiter){
vector<string> ret;
size_t last = 0;
size_t next = 0;
while ((next = s.find(delimiter, last)) != string::npos) {
ret.emplace_back(s.substr (last, next - last));
last = next + 1;
}
ret.emplace_back(s.substr(last));
return ret;
}
If this function doesn't work you can always take a look at How do I iterate over the words of a string?

Split a string in C++ after a space, if more than 1 space leave it in the string

I need to split a string by single spaces and store it into an array of strings. I can achieve this using the fonction boost:split, but what I am not being able to achieve is this:
If there is more than one space, I want to integrate the space in the vector
For example:
(underscore denotes space)
This_is_a_string. gets split into: A[0]=This A[1]=is A[2]=a A[3]=string.
This__is_a_string. gets split into: A[0]=This A[1] =_is A[2]=a A[4]=string.
How can I implement this?
Thanks
For this, you can use a combination of the find and substr functions for string parsing.
Suppose there was just a single space everywhere, then the code would be:
while (str.find(" ") != string::npos)
{
string temp = str.substr(0,str.find(" "));
ans.push_back(temp);
str = str.substr(str.find(" ")+1);
}
The additional request you have raised suggests that we call the find function after we are sure that it is not looking at leading spaces. For this, we can iterate over the leading spaces to count how many there are, and then call the find function to search from thereon. To use the find function from say after x positions (because there are x leading spaces), the call would be str.find(" ",x).
You should also take care of corner cases such as when the entire string is composed of spaces at any point. In that case the while condition in the current form will not terminate. Add the x parameter there as well.
This is by no means the most elegant solution, but it will get the job done:
void bizarre_string_split(const std::string& input,
std::vector<std::string>& output)
{
std::size_t begin_break = 0;
std::size_t end_break = 0;
// count how many spaces we need to add onto the start of the next substring
std::size_t append = 0;
while (end_break != std::string::npos)
{
std::string temp;
end_break = input.find(' ', begin_break);
temp = input.substr(begin_break, end_break - begin_break);
// if the string is empty it is because end_break == begin_break
// this happens because the first char of the substring is whitespace
if (!temp.empty())
{
std::string temp2;
while (append)
{
temp2 += ' ';
--append;
}
temp2 += temp;
output.push_back(temp2);
}
else
{
++append;
}
begin_break = end_break + 1;
}
}

C++ Get String between two delimiter String

Is there any inbuilt function available two get string between two delimiter string in C/C++?
My input look like
_STARTDELIMITER_0_192.168.1.18_STOPDELIMITER_
And my output should be
_0_192.168.1.18_
Thanks in advance...
You can do as:
string str = "STARTDELIMITER_0_192.168.1.18_STOPDELIMITER";
unsigned first = str.find(STARTDELIMITER);
unsigned last = str.find(STOPDELIMITER);
string strNew = str.substr (first,last-first);
Considering your STOPDELIMITER delimiter will occur only once at the end.
EDIT:
As delimiter can occur multiple times, change your statement for finding STOPDELIMITER to:
unsigned last = str.find_last_of(STOPDELIMITER);
This will get you text between the first STARTDELIMITER and LAST STOPDELIMITER despite of them being repeated multiple times.
I have no idea how the top answer received so many votes that it did when the question clearly asks how to get a string between two delimiter strings, and not a pair of characters.
If you would like to do so you need to account for the length of the string delimiter, since it will not be just a single character.
Case 1: Both delimiters are unique:
Given a string _STARTDELIMITER_0_192.168.1.18_STOPDELIMITER_ that you want to extract _0_192.168.1.18_ from, you could modify the top answer like so to get the desired effect. This is the simplest solution without introducing extra dependencies (e.g Boost):
#include <iostream>
#include <string>
std::string get_str_between_two_str(const std::string &s,
const std::string &start_delim,
const std::string &stop_delim)
{
unsigned first_delim_pos = s.find(start_delim);
unsigned end_pos_of_first_delim = first_delim_pos + start_delim.length();
unsigned last_delim_pos = s.find(stop_delim);
return s.substr(end_pos_of_first_delim,
last_delim_pos - end_pos_of_first_delim);
}
int main() {
// Want to extract _0_192.168.1.18_
std::string s = "_STARTDELIMITER_0_192.168.1.18_STOPDELIMITER_";
std::string s2 = "ABC123_STARTDELIMITER_0_192.168.1.18_STOPDELIMITER_XYZ345";
std::string start_delim = "_STARTDELIMITER";
std::string stop_delim = "STOPDELIMITER_";
std::cout << get_str_between_two_str(s, start_delim, stop_delim) << std::endl;
std::cout << get_str_between_two_str(s2, start_delim, stop_delim) << std::endl;
return 0;
}
Will print _0_192.168.1.18_ twice.
It is necessary to add the position of the first delimiter in the second argument to std::string::substr as last - (first + start_delim.length()) to ensure that the it would still extract the desired inner string correctly in the event that the start delimiter is not located at the very beginning of the string, as demonstrated in the second case above.
See the demo.
Case 2: Unique first delimiter, non-unique second delimiter:
Say you want to get a string between a unique delimiter and the first non unique delimiter encountered after the first delimiter. You could modify the above function get_str_between_two_str to use find_first_of instead to get the desired effect:
std::string get_str_between_two_str(const std::string &s,
const std::string &start_delim,
const std::string &stop_delim)
{
unsigned first_delim_pos = s.find(start_delim);
unsigned end_pos_of_first_delim = first_delim_pos + start_delim.length();
unsigned last_delim_pos = s.find_first_of(stop_delim, end_pos_of_first_delim);
return s.substr(end_pos_of_first_delim,
last_delim_pos - end_pos_of_first_delim);
}
If instead you want to capture any characters in between the first unique delimiter and the last encountered second delimiter, like what the asker commented above, use find_last_of instead.
Case 3: Non-unique first delimiter, unique second delimiter:
Very similar to case 2, just reverse the logic between the first delimiter and second delimiter.
Case 4: Both delimiters are not unique:
Again, very similar to case 2, make a container to capture all strings between any of the two delimiters. Loop through the string and update the first delimiter's position to be equal to the second delimiter's position when it is encountered and add the string in between to the container. Repeat until std::string:npos is reached.
To get a string between 2 delimiter strings without white spaces.
string str = "STARTDELIMITER_0_192.168.1.18_STOPDELIMITER";
string startDEL = "STARTDELIMITER";
// this is really only needed for the first delimiter
string stopDEL = "STOPDELIMITER";
unsigned firstLim = str.find(startDEL);
unsigned lastLim = str.find(stopDEL);
string strNew = str.substr (firstLim,lastLim);
//This won't exclude the first delimiter because there is no whitespace
strNew = strNew.substr(firstLim + startDEL.size())
// this will start your substring after the delimiter
I tried combining the two substring functions but it started printing the STOPDELIMITER
Hope that helps
Hope you won't mind I'm answering by another question :)
I would use boost::split or boost::split_iter.
http://www.boost.org/doc/libs/1_54_0/doc/html/string_algo/usage.html#idp166856528
For example code see this SO question:
How to avoid empty tokens when splitting with boost::iter_split?
Let's say you need to get 5th argument (brand) from output below:
zoneid:zonename:state:zonepath:uuid:brand:ip-type:r/w:file-mac-profile
You cannot use any "str.find" function, because it is in the middle, but you can use 'strtok'. e.g.
char *brand;
brand = strtok( line, ":" );
for (int i=0;i<4;i++) {
brand = strtok( NULL, ":" );
}
This is a late answer, but this might work too:
string strgOrg= "STARTDELIMITER_0_192.168.1.18_STOPDELIMITER";
string strg= strgOrg;
strg.replace(strg.find("STARTDELIMITER"), 14, "");
strg.replace(strg.find("STOPDELIMITER"), 13, "");
Hope it works for others.
void getBtwString(std::string oStr, std::string sStr1, std::string sStr2, std::string &rStr)
{
int start = oStr.find(sStr1);
if (start >= 0)
{
string tstr = oStr.substr(start + sStr1.length());
int stop = tstr.find(sStr2);
if (stop >1)
rStr = oStr.substr(start + sStr1.length(), stop);
else
rStr ="error";
}
else
rStr = "error"; }
or if you are using Windows and have access to c++14, the following,
void getBtwString(std::string oStr, std::string sStr1, std::string sStr2, std::string &rStr)
{
using namespace std::literals::string_literals;
auto start = sStr1;
auto end = sStr2;
std::regex base_regex(start + "(.*)" + end);
auto example = oStr;
std::smatch base_match;
std::string matched;
if (std::regex_search(example, base_match, base_regex)) {
if (base_match.size() == 2) {
matched = base_match[1].str();
}
rStr = matched;
}
}
Example:
string strout;
getBtwString("it's_12345bb2","it's","bb2",strout);
getBtwString("it's_12345bb2"s,"it's"s,"bb2"s,strout); // second solution
Headers:
#include <regex> // second solution
#include <string.h>

Replace whole words in a string list without using external libraries

I want to replace some words without using external libraries.
My first attempt was to make a copy of the string, but it was not efficient, so this is another attempt where I use addresses:
void ReplaceString(std::string &subject, const std::string &search, const std::string &replace)
{
size_t position = 0;
while ((position = subject.find(search, position)) != std::string::npos) //if something messes up --> failure
{
subject.replace(position, search.length(), replace);
position = position + replace.length();
}
}
Because this is not very efficient either, I want to use another thing, but I got stuck; I want to use a function like replace_stuff(std::string & a); with a single parameter using string.replace() and string.find() (parsing it with a for loop or something) and then make use of std::map <std::string,std::string>; which is very convenient for me.
I want to use it for a large number of input words. (let's say replacing many bad words with some harmless ones)
The problem with your question is the lack of the necessary components in the Standard library. If you want an efficient implementation, you'd probably need a trie for efficient lookups. Writing one as part of the answer would be way to much code.
If you use a std::map or, if C++11 is available in your environment, a std::unordered_map, you will need to utilitize additional information about the input string and the search-replace pairs from the map. You'd then tokenize the string and check each token if it has to be replaced. Using positions pointing in the input string is a good idea since it avoids copying data. Which brings us to:
Efficiency will depend on memory access (reads and writes), so you should not modify the input string. Create the output by starting with an empty string and by appending pieces from the input. Check each part of the input: If it is a word, check if it needs to be replaced or if it is appended to the output unmodified. If it is not part of a word, append it unmodified.
It sounds like you want to replace all the "bad" words in a string with harmless ones, but your current implementation is inefficient because the list of bad words is much larger than the length of your input string (subject). Is this correct?
If so, the following code should make it more efficient. As you can see, I had to pass the map as a parameter, but if your function is going to be part of a class, you don't need to do so.
void ReplaceString(std::string &subject, const std::map<std::string, std::string>& replace_map)
{
size_t startofword = 0, endofword = 0;
while(startofword < subject.size())
{
size_t length = std::string::npos;
//get next word in string
endofword = subject.find_first_of(" ", startofword);
if(endofword != std::string::npos)
length = endofword-startofword;
std::string search = subject.substr(startofword, length);
//try to find this word in the map
if(replace_map.find(search) != replace_map.end())
{
//if found, replace the word with a new word
subject.replace(startofword, length, replace_map[search]);
startofword += replace_map[search].length();
}
else
{
startofword += length;
}
}
}
I use the following functions, hope it helps:
//=============================================================================
//replaces each occurence of the phrase in sWhat with sReplacement
std::string& sReplaceAll(std::string& sS, const std::string& sWhat, const std::string& sReplacement)
{
size_t pos = 0, fpos;
while ((fpos = sS.find(sWhat, pos)) != std::string::npos)
{
sS.replace(fpos, sWhat.size(), sReplacement);
pos = fpos + sReplacement.length();
}
return sS;
}
//=============================================================================
// replaces each single char from sCharList that is found within sS with entire sReplacement
std::string& sReplaceChars(std::string& sS, const std::string& sCharList, const std::string& sReplacement)
{
size_t pos=0;
while (pos < sS.length())
{
if (sCharList.find(sS.at(pos),0)!=std::string::npos) //pos is where a charlist-char was found
{
sS.replace(pos, 1, sReplacement);
pos += sReplacement.length()-1;
}
pos++;
}
return sS;
}
You might create a class, say Replacer:
class Replacer
{
std::map<std::string,> replacement;
public:
Replacer()
{
// init the map here
replacement.insert ( std::pair<std::string,std::string>("C#","C++") );
//...
}
void replace_stuff(std::string & a);
}
Then the replace_stuff definition would be very similar to your original ReplaceString (it would use map entries instead of the passed parameters).

Cleaning a string of punctuation in C++

Ok so before I even ask my question I want to make one thing clear. I am currently a student at NIU for Computer Science and this does relate to one of my assignments for a class there. So if anyone has a problem read no further and just go on about your business.
Now for anyone who is willing to help heres the situation. For my current assignment we have to read a file that is just a block of text. For each word in the file we are to clear any punctuation in the word (ex : "can't" would end up as "can" and "that--to" would end up as "that" obviously with out the quotes, quotes were used just to specify what the example was).
The problem I've run into is that I can clean the string fine and then insert it into the map that we are using but for some reason with the code I have written it is allowing an empty string to be inserted into the map. Now I've tried everything that I can come up with to stop this from happening and the only thing I've come up with is to use the erase method within the map structure itself.
So what I am looking for is two things, any suggestions about how I could a) fix this with out simply just erasing it and b) any improvements that I could make on the code I already have written.
Here are the functions I have written to read in from the file and then the one that cleans it.
Note: the function that reads in from the file calls the clean_entry function to get rid of punctuation before anything is inserted into the map.
Edit: Thank you Chris. Numbers are allowed :). If anyone has any improvements to the code I've written or any criticisms of something I did I'll listen. At school we really don't get feed back on the correct, proper, or most efficient way to do things.
int get_words(map<string, int>& mapz)
{
int cnt = 0; //set out counter to zero
map<string, int>::const_iterator mapzIter;
ifstream input; //declare instream
input.open( "prog2.d" ); //open instream
assert( input ); //assure it is open
string s; //temp strings to read into
string not_s;
input >> s;
while(!input.eof()) //read in until EOF
{
not_s = "";
clean_entry(s, not_s);
if((int)not_s.length() == 0)
{
input >> s;
clean_entry(s, not_s);
}
mapz[not_s]++; //increment occurence
input >>s;
}
input.close(); //close instream
for(mapzIter = mapz.begin(); mapzIter != mapz.end(); mapzIter++)
cnt = cnt + mapzIter->second;
return cnt; //return number of words in instream
}
void clean_entry(const string& non_clean, string& clean)
{
int i, j, begin, end;
for(i = 0; isalnum(non_clean[i]) == 0 && non_clean[i] != '\0'; i++);
begin = i;
if(begin ==(int)non_clean.length())
return;
for(j = begin; isalnum(non_clean[j]) != 0 && non_clean[j] != '\0'; j++);
end = j;
clean = non_clean.substr(begin, (end-begin));
for(i = 0; i < (int)clean.size(); i++)
clean[i] = tolower(clean[i]);
}
The problem with empty entries is in your while loop. If you get an empty string, you clean the next one, and add it without checking. Try changing:
not_s = "";
clean_entry(s, not_s);
if((int)not_s.length() == 0)
{
input >> s;
clean_entry(s, not_s);
}
mapz[not_s]++; //increment occurence
input >>s;
to
not_s = "";
clean_entry(s, not_s);
if((int)not_s.length() > 0)
{
mapz[not_s]++; //increment occurence
}
input >>s;
EDIT: I notice you are checking if the characters are alphanumeric. If numbers are not allowed, you may need to revisit that area as well.
Further improvements would be to
declare variables only when you use them, and in the innermost scope
use c++-style casts instead of the c-style (int) casts
use empty() instead of length() == 0 comparisons
use the prefix increment operator for the iterators (i.e. ++mapzIter)
A blank string is a valid instance of the string class, so there's nothing special about adding it into the map. What you could do is first check if it's empty, and only increment in that case:
if (!not_s.empty())
mapz[not_s]++;
Style-wise, there's a few things I'd change, one would be to return clean from clean_entry instead of modifying it:
string not_s = clean_entry(s);
...
string clean_entry(const string &non_clean)
{
string clean;
... // as before
if(begin ==(int)non_clean.length())
return clean;
... // as before
return clean;
}
This makes it clearer what the function is doing (taking a string, and returning something based on that string).
The function 'getWords' is doing a lot of distinct actions that could be split out into other functions. There's a good chance that by splitting it up into it's individual parts, you would have found the bug yourself.
From the basic structure, I think you could split the code into (at least):
getNextWord: Return the next (non blank) word from the stream (returns false if none left)
clean_entry: What you have now
getNextCleanWord: Calls getNextWord, and if 'true' calls CleanWord. Returns 'false' if no words left.
The signatures of 'getNextWord' and 'getNextCleanWord' might look something like:
bool getNextWord (std::ifstream & input, std::string & str);
bool getNextCleanWord (std::ifstream & input, std::string & str);
The idea is that each function does a smaller more distinct part of the problem. For example, 'getNextWord' does nothing but get the next non blank word (if there is one). This smaller piece therefore becomes an easier part of the problem to solve and debug if necessary.
The main component of 'getWords' then can be simplified down to:
std::string nextCleanWord;
while (getNextCleanWord (input, nextCleanWord))
{
++map[nextCleanWord];
}
An important aspect to development, IMHO, is to try to Divide and Conquer the problem. Split it up into the individual tasks that need to take place. These sub-tasks will be easier to complete and should also be easier to maintain.