How do I compare a section of a string without copying? - c++

I have a long string that I'm iterating through, and at each iteration I compare a section of the string to a constant and store some parts of the string. In my actual code, this code runs millions of times and is the main bottleneck. I think it's due to the excessive use of std::string::substr.
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
std::vector<std::string> out0;
std::map<std::string, std::string> out;
size_t pos = str.find(',');
// loop over the string, collecting "key=value" pairs
while (pos < str.size() - 1) {
if (str.substr(pos + 1, 2) == "0=") {
auto newPos = str.find(',', pos + 3);
out0.push_back(str.substr(pos + 3, newPos - pos - 3);
pos = newPos;
} else {
size_t eqPos = str.find('=', pos + 1);
auto newPos = str.find(',', eqPos + 1);
out[str.substr(pos + 1, eqPos - pos - 1)] = str.substr(eqPos + 1, newPos - eqPos - 1);
}
}
// print out the data structures (this doesn't happen in my actual code)
std::cout << "out0:";
for (auto& entry : out0) {
std::cout << ' ' << entry;
}
std::cout << std::endl;
std::cout << "out:";
for (auto it : out) {
std::cout << ' ' << it->first << '=' << it->second;
}
}
Here are my questions:
How can I perform comparisons on the string without performing a copy and without writing the comparison for each character, e.g. str[pos + 1] == '0' && str[pos + 2] == '=' && ...?
How can I store references to substrings, instead of making copies every time I add to out0 and out?
This may be a great case for the use of char *, but I've never used it before.
Edit:
Unfortunately, I've only got C++11; otherwise, std::string_view is the best answer. Is there a way to accomplish the storage of references without std::string_view?

If you have C++17, you can use string_view thus: (untested code):
string_view sv{str.data() + pos, 2};
if (sv == "0=") ...
No copies. Or even (all in one go):
if (string_view{str.data() + pos, 2} == "0=") ...
If you don't have string_view, you can use char_traits:
if (std::char_traits<char>::compare(str.data() + pos, "0=", 2) == 0) ...

Since people have posted std::string_view, here is the plain old C pointers version.
(Didn't test though, but it'll give you the idea)
See below:
std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
std::string substr("test");
.
. Inside some function
.
const char *str_p = str.c_str(); // String you want to compare with a substring
const char *substr_p = substr.c_str(); // Your substring
size_t str_len = str.length();
size_t substr_len = substr.length();
bool comparison_result = true;
for(size_t i = 0; i < str_len - substr_len; i++) {
for(size_t j = 0; j < substr_len; j++) {
if(*(str_p + i + j) != *(substr_p + j)) {
comparison_result = false;
break;
}
if (j == substr_len - 1) { // We can only reach here when substring is hit
comparison_result = true;
i = str_len - substr_len;
break;
}
}
}
return comparison_result;
EDIT:
Due to #Toby Speight's suggestion in the comments (which I find very nice), I'm implementing a std::memcmp() version as well. In that case, the inner loop becomes:
.
. Inside some function
.
const char *str_p = str.c_str(); // String you want to compare with a substring
const char *substr_p = substr.c_str(); // Your substring
size_t str_len = str.length();
size_t substr_len = substr.length();
bool comparison_result = false;
for(size_t i = 0; i < str_len - substr_len; i++) {
if(std::memcmp(str_p + i, substr_p, substr_len) == 0) {
comparison_result = true;
break;
}
}
return comparison_result;
EDIT:
We got another request, this time from #Alexander Zhang, let's implement it:
.
. Inside some function
.
const char *str_p = str.c_str(); // String you want to compare with a substring
const char *substr_p = substr.c_str(); // Your substring
size_t str_len = str.length();
size_t substr_len = substr.length();
bool comparison_result = false;
for(size_t i = 0; i < str_len - substr_len; i++) {
if(std::memcmp(&str_p[i], &substr_p[0], substr_len) == 0) {
comparison_result = true;
break;
}
}
return comparison_result;

Use a std::string_view instead of std::string for the key and value of of out. std::string_view holds a pointer to the string, and a size of the string, so it is very light weight. This lets you extract the information you need, but without having to copy any of the characters in string and any potential memory allocations of creating those strings.
What you'll need to do is get a string_view from the std::string, and then use that string_view to get all of the sub strings you need.

std::string has compare() methods that take a const char* substring as input. You don't need to use std::string::substr() to compare substrings, eg:
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
std::vector<std::string> out0;
std::map<std::string, std::string> out;
size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
// loop over the string, collecting "key=value" pairs
while (startPos < str.size()){
nameStart = startPos;
delimPos = str.find_first_of("=,", startPos, 2);
if (delimPos == std::string::npos) {
nameEnd = valueStart = valueEnd = str.size();
}
else {
nameEnd = delimPos;
if (str[delimPos] == '=') {
valueStart = nameEnd + 1;
valueEnd = str.find(',', valueStart);
if (valueEnd == std::string::npos) {
valueEnd = str.size();
}
}
else {
valueStart = valueEnd = nameEnd;
}
}
// TODO: if needed, adjust name(Start|End) and value(Start|End) to
// ignore leading/trailing whitespace around the name and value
// substrings...
if (str.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
out0.push_back(str.substr(valueStart, valueEnd - valueStart));
} else {
out[str.substr(nameStart, nameEnd - nameStart)] = str.substr(valueStart, valueEnd - valueStart);
}
startPos = valueEnd + 1;
}
// print out the data structures
std::cout << "out0:";
for (auto& entry : out0) {
std::cout << ' ' << entry;
}
std::cout << std::endl;
std::cout << "out:";
for (auto it : out) {
std::cout << ' ' << it->first << '=' << it->second;
}
}
Output:
out0: My with
out: 1=comma 2=separated 3=repeated 7=IDs
Live Demo
You could take this a step further to eliminate the use of substr() altogether by not storing std::string values in your std::vector and std::map at all, but rather store std::pair<char*, size_t>:
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <utility>
using StrView = std::pair<const char*, size_t>;
StrView makeStrView(const char *str, size_t size) {
return std::make_pair(str, size);
}
struct compareStrView {
bool operator()(const StrView &lhs, const StrView &rhs) const {
if (lhs.second == rhs.second)
return (std::char_traits<char>::compare(lhs.first, rhs.first, lhs.second) < 0);
return (lhs.second < rhs.second);
}
};
std::ostream& operator<<(std::ostream &os, const StrView &rhs) {
return os.write(rhs.first, rhs.second);
}
int main() {
std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
std::vector<StrView> out0;
std::map<StrView, StrView, compareStrView> out;
size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
// loop over the string, collecting "key=value" pairs
while (startPos < str.size()){
nameStart = startPos;
delimPos = str.find_first_of("=,", startPos, 2);
if (delimPos == std::string::npos) {
nameEnd = valueStart = valueEnd = str.size();
}
else {
nameEnd = delimPos;
if (str[delimPos] == '=') {
valueStart = nameEnd + 1;
valueEnd = str.find(',', valueStart);
if (valueEnd == std::string::npos) {
valueEnd = str.size();
}
}
else {
valueStart = valueEnd = nameEnd;
}
}
// TODO: if needed, adjust nameStart/End and valueStartEnd to
// ignore leading/trailing whitespace around the name and value
// substrings...
if (str.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
out0.push_back(makeStrView(&str[valueStart], valueEnd - valueStart));
} else {
out[makeStrView(&str[nameStart], nameEnd - nameStart)] = makeStrView(&str[valueStart], valueEnd - valueStart);
}
startPos = valueEnd + 1;
}
// print out the data structures
std::cout << "out0:";
for (auto& entry : out0) {
std::cout << ' ' << entry;
}
std::cout << std::endl;
std::cout << "out:";
for (auto &it : out) {
std::cout << ' ' << it.first << '=' << it.second;
}
}
Output:
out0: My with
out: 1=comma 2=separated 3=repeated 7=IDs
Live Demo
In C++17, you can use std::string_view instead:
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <string_view>
int main() {
std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
std::string_view sv(str);
std::vector<std::string_view> out0;
std::map<std::string_view, std::string_view> out;
size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
// loop over the string, collecting "key=value" pairs
while (startPos < sv.size()){
nameStart = startPos;
delimPos = sv.find_first_of("=,", startPos, 2);
if (delimPos == std::string_view::npos) {
nameEnd = valueStart = valueEnd = sv.size();
}
else {
nameEnd = delimPos;
if (sv[delimPos] == '=') {
valueStart = nameEnd + 1;
valueEnd = sv.find(',', valueStart);
if (valueEnd == std::string_view::npos) {
valueEnd = sv.size();
}
}
else {
valueStart = valueEnd = nameEnd;
}
}
// TODO: if needed, adjust nameStart/End and valueStartEnd to
// ignore leading/trailing whitespace around the name and value
// substrings...
if (sv.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
out0.push_back(sv.substr(valueStart, valueEnd - valueStart));
} else {
out[sv.substr(nameStart, nameEnd - nameStart)] = sv.substr(valueStart, valueEnd - valueStart);
}
startPos = valueEnd + 1;
}
// print out the data structures
std::cout << "out0:";
for (auto& entry : out0) {
std::cout << ' ' << entry;
}
std::cout << std::endl;
std::cout << "out:";
for (auto &it : out) {
std::cout << ' ' << it.first << '=' << it.second;
}
}

You can try to use Regex to split the value pair tuples.
Although haven't tested if any faster
This expression should do the trick, just get all the match (all the pairs)
(?:(\d)+=(?:([^,]*),?))*?
https://regex101.com/r/PDZMq0/1

Related

How to scan several strings with spaces? [duplicate]

I am parsing a string in C++ using the following:
using namespace std;
string parsed,input="text to be parsed";
stringstream input_stringstream(input);
if (getline(input_stringstream,parsed,' '))
{
// do some processing.
}
Parsing with a single char delimiter is fine. But what if I want to use a string as delimiter.
Example: I want to split:
scott>=tiger
with >= as delimiter so that I can get scott and tiger.
You can use the std::string::find() function to find the position of your string delimiter, then use std::string::substr() to get a token.
Example:
std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
The find(const string& str, size_t pos = 0) function returns the position of the first occurrence of str in the string, or npos if the string is not found.
The substr(size_t pos = 0, size_t n = npos) function returns a substring of the object, starting at position pos and of length npos.
If you have multiple delimiters, after you have extracted one token, you can remove it (delimiter included) to proceed with subsequent extractions (if you want to preserve the original string, just use s = s.substr(pos + delimiter.length());):
s.erase(0, s.find(delimiter) + delimiter.length());
This way you can easily loop to get each token.
Complete Example
std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";
size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
std::cout << token << std::endl;
s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;
Output:
scott
tiger
mushroom
For string delimiter
Split string based on a string delimiter. Such as splitting string "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih" based on string delimiter "-+", output will be {"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}
#include <iostream>
#include <sstream>
#include <vector>
// for string delimiter
std::vector<std::string> split(std::string s, std::string delimiter) {
size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token;
std::vector<std::string> res;
while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) {
token = s.substr (pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len;
res.push_back (token);
}
res.push_back (s.substr (pos_start));
return res;
}
int main() {
std::string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
std::string delimiter = "-+";
std::vector<std::string> v = split (str, delimiter);
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih
For single character delimiter
Split string based on a character delimiter. For example, splitting string "adsf+qwer+poui+fdgh" with delimiter "+" will output {"adsf", "qwer", "poui", "fdgh"}
#include <iostream>
#include <sstream>
#include <vector>
std::vector<std::string> split (const std::string &s, char delim) {
std::vector<std::string> result;
std::stringstream ss (s);
std::string item;
while (getline (ss, item, delim)) {
result.push_back (item);
}
return result;
}
int main() {
std::string str = "adsf+qwer+poui+fdgh";
std::vector<std::string> v = split (str, '+');
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwer
poui
fdgh
This method uses std::string::find without mutating the original string by remembering the beginning and end of the previous substring token.
#include <iostream>
#include <string>
int main()
{
std::string s = "scott>=tiger";
std::string delim = ">=";
auto start = 0U;
auto end = s.find(delim);
while (end != std::string::npos)
{
std::cout << s.substr(start, end - start) << std::endl;
start = end + delim.length();
end = s.find(delim, start);
}
std::cout << s.substr(start, end);
}
You can use next function to split string:
vector<string> split(const string& str, const string& delim)
{
vector<string> tokens;
size_t prev = 0, pos = 0;
do
{
pos = str.find(delim, prev);
if (pos == string::npos) pos = str.length();
string token = str.substr(prev, pos-prev);
if (!token.empty()) tokens.push_back(token);
prev = pos + delim.length();
}
while (pos < str.length() && prev < str.length());
return tokens;
}
You can also use regex for this:
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::regex regexz(regex_str);
std::vector<std::string> list(std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator());
return list;
}
which is equivalent to :
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::sregex_token_iterator token_iter(str.begin(), str.end(), regexz, -1);
std::sregex_token_iterator end;
std::vector<std::string> list;
while (token_iter != end)
{
list.emplace_back(*token_iter++);
}
return list;
}
and use it like this :
#include <iostream>
#include <string>
#include <regex>
std::vector<std::string> split(const std::string str,
const std::string regex_str) {
std::regex regexz(regex_str);
return {std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator()};
}
int main()
{
std::string input_str = "lets split this";
std::string regex_str = " ";
auto tokens = split(input_str, regex_str);
for (auto& item: tokens)
{
std::cout<<item <<std::endl;
}
}
play with it online!
you can simply use substrings, characters, etc like normal, or use actual regular expressions to do the splitting.
its also concise and C++11!
A way of doing it with C++20:
#include <iostream>
#include <ranges>
#include <string_view>
int main()
{
std::string hello = "text to be parsed";
auto split = hello
| std::ranges::views::split(' ')
| std::ranges::views::transform([](auto&& str) { return std::string_view(&*str.begin(), std::ranges::distance(str)); });
for (auto&& word : split)
{
std::cout << word << std::endl;
}
}
See:
https://stackoverflow.com/a/48403210/10771848
https://en.cppreference.com/w/cpp/ranges/split_view
This code splits lines from text, and add everyone into a vector.
vector<string> split(char *phrase, string delimiter){
vector<string> list;
string s = string(phrase);
size_t pos = 0;
string token;
while ((pos = s.find(delimiter)) != string::npos) {
token = s.substr(0, pos);
list.push_back(token);
s.erase(0, pos + delimiter.length());
}
list.push_back(s);
return list;
}
Called by:
vector<string> listFilesMax = split(buffer, "\n");
Answer is already there, but selected-answer uses erase function which is very costly, think of some very big string(in MBs). Therefore I use below function.
vector<string> split(const string& i_str, const string& i_delim)
{
vector<string> result;
size_t startIndex = 0;
for (size_t found = i_str.find(i_delim); found != string::npos; found = i_str.find(i_delim, startIndex))
{
result.emplace_back(i_str.begin()+startIndex, i_str.begin()+found);
startIndex = found + i_delim.size();
}
if (startIndex != i_str.size())
result.emplace_back(i_str.begin()+startIndex, i_str.end());
return result;
}
strtok allows you to pass in multiple chars as delimiters. I bet if you passed in ">=" your example string would be split correctly (even though the > and = are counted as individual delimiters).
EDIT if you don't want to use c_str() to convert from string to char*, you can use substr and find_first_of to tokenize.
string token, mystring("scott>=tiger");
while(token != mystring){
token = mystring.substr(0,mystring.find_first_of(">="));
mystring = mystring.substr(mystring.find_first_of(">=") + 1);
printf("%s ",token.c_str());
}
I would use boost::tokenizer. Here's documentation explaining how to make an appropriate tokenizer function: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm
Here's one that works for your case.
struct my_tokenizer_func
{
template<typename It>
bool operator()(It& next, It end, std::string & tok)
{
if (next == end)
return false;
char const * del = ">=";
auto pos = std::search(next, end, del, del + 2);
tok.assign(next, pos);
next = pos;
if (next != end)
std::advance(next, 2);
return true;
}
void reset() {}
};
int main()
{
std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
std::cout << i << '\n';
}
This should work perfectly for string (or single character) delimiters. Don't forget to include #include <sstream>.
std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+";
std::istringstream ss(input);
std::string token;
std::string::iterator it;
while(std::getline(ss, token, *(it = delimiter.begin()))) {
std::cout << token << std::endl; // Token is extracted using '='
it++;
// Skip the rest of delimiter if exists ",+"
while(it != delimiter.end() and ss.peek() == *(it)) {
it++; ss.get();
}
}
The first while loop extracts a token using the first character of the string delimiter. The second while loop skips the rest of the delimiter and stops at the beginning of the next token.
Here's my take on this. It handles the edge cases and takes an optional parameter to remove empty entries from the results.
bool endsWith(const std::string& s, const std::string& suffix)
{
return s.size() >= suffix.size() &&
s.substr(s.size() - suffix.size()) == suffix;
}
std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool removeEmptyEntries = false)
{
std::vector<std::string> tokens;
for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
{
size_t position = s.find(delimiter, start);
end = position != std::string::npos ? position : s.length();
std::string token = s.substr(start, end - start);
if (!removeEmptyEntries || !token.empty())
{
tokens.push_back(token);
}
}
if (!removeEmptyEntries &&
(s.empty() || endsWith(s, delimiter)))
{
tokens.push_back("");
}
return tokens;
}
Examples
split("a-b-c", "-"); // [3]("a","b","c")
split("a--c", "-"); // [3]("a","","c")
split("-b-", "-"); // [3]("","b","")
split("--c--", "-"); // [5]("","","c","","")
split("--c--", "-", true); // [1]("c")
split("a", "-"); // [1]("a")
split("", "-"); // [1]("")
split("", "-", true); // [0]()
A very simple/naive approach:
vector<string> words_seperate(string s){
vector<string> ans;
string w="";
for(auto i:s){
if(i==' '){
ans.push_back(w);
w="";
}
else{
w+=i;
}
}
ans.push_back(w);
return ans;
}
Or you can use boost library split function:
vector<string> result;
boost::split(result, input, boost::is_any_of("\t"));
Or You can try TOKEN or strtok:
char str[] = "DELIMIT-ME-C++";
char *token = strtok(str, "-");
while (token)
{
cout<<token;
token = strtok(NULL, "-");
}
Or You can do this:
char split_with=' ';
vector<string> words;
string token;
stringstream ss(our_string);
while(getline(ss , token , split_with)) words.push_back(token);
Just in case in the future, someone wants out of the box function of Vincenzo Pii 's answer
#include <vector>
#include <string>
std::vector<std::string> SplitString(
std::string str,
std::string delimeter)
{
std::vector<std::string> splittedStrings = {};
size_t pos = 0;
while ((pos = str.find(delimeter)) != std::string::npos)
{
std::string token = str.substr(0, pos);
if (token.length() > 0)
splittedStrings.push_back(token);
str.erase(0, pos + delimeter.length());
}
if (str.length() > 0)
splittedStrings.push_back(str);
return splittedStrings;
}
I also fixed some bugs so that the function won't return an empty string if there is a delimiter at the start or the end of the string
This is a complete method that splits the string on any delimiter and returns a vector of the chopped up strings.
It is an adaptation from the answer from ryanbwork. However, his check for: if(token != mystring) gives wrong results if you have repeating elements in your string. This is my solution to that problem.
vector<string> Split(string mystring, string delimiter)
{
vector<string> subStringList;
string token;
while (true)
{
size_t findfirst = mystring.find_first_of(delimiter);
if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
{
subStringList.push_back(mystring); //push back the final piece of mystring
return subStringList;
}
token = mystring.substr(0, mystring.find_first_of(delimiter));
mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
subStringList.push_back(token);
}
return subStringList;
}
Since this is the top-rated Stack Overflow Google search result for C++ split string or similar, I'll post a complete, copy/paste runnable example that shows both methods.
splitString uses stringstream (probably the better and easier option in most cases)
splitString2 uses find and substr (a more manual approach)
// SplitString.cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
// function prototypes
std::vector<std::string> splitString(const std::string& str, char delim);
std::vector<std::string> splitString2(const std::string& str, char delim);
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx);
int main(void)
{
// Test cases - all will pass
std::string str = "ab,cd,ef";
//std::string str = "abcdef";
//std::string str = "";
//std::string str = ",cd,ef";
//std::string str = "ab,cd,"; // behavior of splitString and splitString2 is different for this final case only, if this case matters to you choose which one you need as applicable
std::vector<std::string> tokens = splitString(str, ',');
std::cout << "tokens: " << "\n";
if (tokens.empty())
{
std::cout << "(tokens is empty)" << "\n";
}
else
{
for (auto& token : tokens)
{
if (token == "") std::cout << "(empty string)" << "\n";
else std::cout << token << "\n";
}
}
return 0;
}
std::vector<std::string> splitString(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
std::string currentToken;
std::stringstream ss(str);
while (std::getline(ss, currentToken, delim))
{
tokens.push_back(currentToken);
}
return tokens;
}
std::vector<std::string> splitString2(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
int leftIdx = 0;
int delimIdx = str.find(delim);
int rightIdx;
while (delimIdx != std::string::npos)
{
rightIdx = delimIdx - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
// prep for next time around
leftIdx = delimIdx + 1;
delimIdx = str.find(delim, delimIdx + 1);
}
rightIdx = str.size() - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
return tokens;
}
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx)
{
return str.substr(leftIdx, rightIdx - leftIdx + 1);
}
Yet another answer: Here I'm using find_first_not_of string function which returns the position of the first character that does not match any of the characters specified in the delim.
size_t find_first_not_of(const string& delim, size_t pos = 0) const noexcept;
Example:
int main()
{
size_t start = 0, end = 0;
std::string str = "scott>=tiger>=cat";
std::string delim = ">=";
while ((start = str.find_first_not_of(delim, end)) != std::string::npos)
{
end = str.find(delim, start); // finds the 'first' occurance from the 'start'
std::cout << str.substr(start, end - start)<<std::endl; // extract substring
}
return 0;
}
Output:
scott
tiger
cat
I make this solution. It is very simple, all the prints/values are in the loop (no need to check after the loop).
#include <iostream>
#include <string>
using std::cout;
using std::string;
int main() {
string s = "it-+is-+working!";
string d = "-+";
int firstFindI = 0;
int secendFindI = 0;
while (secendFindI != string::npos)
{
secendFindI = s.find(d, firstFindI);
cout << s.substr(firstFindI, secendFindI - firstFindI) << "\n"; // print sliced part
firstFindI = secendFindI + d.size(); // add to the search index
}
}
Thanks to #SteveWard for improving this answer.
This is similar to other answers but it's using string_view. So these are just views for the original string. Similar to the c++20 example. Though this would be a c++17 example. (edit to skip empty matches)
#include <algorithm>
#include <iostream>
#include <string_view>
#include <vector>
std::vector<std::string_view> split(std::string_view buffer,
const std::string_view delimeter = " ") {
std::vector<std::string_view> ret{};
std::decay_t<decltype(std::string_view::npos)> pos{};
while ((pos = buffer.find(delimeter)) != std::string_view::npos) {
const auto match = buffer.substr(0, pos);
if (!match.empty()) ret.push_back(match);
buffer = buffer.substr(pos + delimeter.size());
}
if (!buffer.empty()) ret.push_back(buffer);
return ret;
}
int main() {
const auto split_values = split("1 2 3 4 5 6 7 8 9 10 ");
std::for_each(split_values.begin(), split_values.end(),
[](const auto& str) { std::cout << str << '\n'; });
return split_values.size();
}
If you do not want to modify the string (as in the answer by Vincenzo Pii) and want to output the last token as well, you may want to use this approach:
inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
std::vector<std::string> ret;
size_t start = 0;
size_t end = 0;
size_t len = 0;
std::string token;
do{ end = s.find(delimiter,start);
len = end - start;
token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
std::cout << token << std::endl;
}while ( end != std::string::npos );
return ret;
}
Here's a concise split function. I decided to have back to back delimiters return as an empty string but you could easily check that if the substring is empty and not add it to the vector if it is.
#include <vector>
#include <string>
using namespace std;
vector<string> split(string to_split, string delimiter) {
size_t pos = 0;
vector<string> matches{};
do {
pos = to_split.find(delimiter);
int change_end;
if (pos == string::npos) {
pos = to_split.length() - 1;
change_end = 1;
}
else {
change_end = 0;
}
matches.push_back(to_split.substr(0, pos+change_end));
to_split.erase(0, pos+1);
}
while (!to_split.empty());
return matches;
}
This method use string find and string substr
vector<string> split(const string& str,const string delim){
vector<string> vtokens;
size_t start = 0;
size_t end = 0;
while((end = str.find(delim,start))!=string::npos){
vtokens.push_back(str.substr(start,end-start));
start = end +1;
}
vtokens.push_back(str.substr(start));
return vtokens;
}
#include<iostream>
#include<algorithm>
using namespace std;
int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}
void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}
int main(){
string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);
for(int i=0;i<x;i++)
cout<<res[i]<<endl;
return 0;
}
P.S: Works only if the lengths of the strings after splitting are equal
std::vector<std::string> parse(std::string str,std::string delim){
std::vector<std::string> tokens;
char *str_c = strdup(str.c_str());
char* token = NULL;
token = strtok(str_c, delim.c_str());
while (token != NULL) {
tokens.push_back(std::string(token));
token = strtok(NULL, delim.c_str());
}
delete[] str_c;
return tokens;
}
Function:
std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
std::vector<std::string> vRet;
size_t nPos = 0;
size_t nLen = sWhat.length();
size_t nDelimLen = sDelim.length();
while (nPos < nLen) {
std::size_t nFoundPos = sWhat.find(sDelim, nPos);
if (nFoundPos != std::string::npos) {
std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
vRet.push_back(sToken);
nPos = nFoundPos + nDelimLen;
if (nFoundPos + nDelimLen == nLen) { // last delimiter
vRet.push_back("");
}
} else {
std::string sToken = sWhat.substr(nPos, nLen - nPos);
vRet.push_back(sToken);
break;
}
}
return vRet;
}
Unit-tests:
bool UnitTestSplit::run() {
bool bTestSuccess = true;
struct LTest {
LTest(
const std::string &sStr,
const std::string &sDelim,
const std::vector<std::string> &vExpectedVector
) {
this->sStr = sStr;
this->sDelim = sDelim;
this->vExpectedVector = vExpectedVector;
};
std::string sStr;
std::string sDelim;
std::vector<std::string> vExpectedVector;
};
std::vector<LTest> tests;
tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));
for (int i = 0; i < tests.size(); i++) {
LTest test = tests[i];
std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
for (int n = 0; n < nMin; n++) {
compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
}
}
return bTestSuccess;
}
i use pointer arithmetic. inner while for string delimeter if you satify with char delim just remove inner while simply. i hope it is correct. if you notice any mistake or improve please leave the comment.
std::vector<std::string> split(std::string s, std::string delim)
{
char *p = &s[0];
char *d = &delim[0];
std::vector<std::string> res = {""};
do
{
bool is_delim = true;
char *pp = p;
char *dd = d;
while (*dd && is_delim == true)
if (*pp++ != *dd++)
is_delim = false;
if (is_delim)
{
p = pp - 1;
res.push_back("");
}
else
*(res.rbegin()) += *p;
} while (*p++);
return res;
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.push_back(std::forward<T>(t)), void()) {
c.push_back(std::forward<T>(t));
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.insert(std::forward<T>(t)), void()) {
c.insert(std::forward<T>(t));
}
template<typename Container>
Container splitR(const std::string& input, const std::string& delims) {
Container out;
size_t delims_len = delims.size();
auto begIdx = 0u;
auto endIdx = input.find(delims, begIdx);
if (endIdx == std::string::npos && input.size() != 0u) {
insert_in_container(out, input);
}
else {
size_t w = 0;
while (endIdx != std::string::npos) {
w = endIdx - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
begIdx = endIdx + delims_len;
endIdx = input.find(delims, begIdx);
}
w = input.length() - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
}
return out;
}
A simpler solution would be -
You can use strtok to delimit on the basis of multichar delimiter.
Remember to use strdup so that the orignal string isn't mutated.
#include <stdio.h>
#include <string.h>
const char* str = "scott>=tiger";
char *token = strtok(strdup(str), ">=");
while (token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, ">=");
}
I looked through the answers and haven't seen an iterator based approach that can be fed into a range loop, so I made one.
This uses C++17 string_views so it shouldn't allocate copies of the string.
struct StringSplit
{
struct Iterator
{
size_t tokenStart_ = 0;
size_t tokenEnd_ = 0;
std::string str_;
std::string_view view_;
std::string delimiter_;
bool done_ = false;
Iterator()
{
// End iterator.
done_ = true;
}
Iterator(std::string str, std::string delimiter)
: str_{std::move(str)}, view_{str_}, delimiter_{
std::move(delimiter)}
{
tokenEnd_ = view_.find(delimiter_, tokenStart_);
}
std::string_view operator*()
{
return view_.substr(tokenStart_, tokenEnd_ - tokenStart_);
}
Iterator &operator++()
{
if (tokenEnd_ == std::string::npos)
{
done_ = true;
return *this;
}
tokenStart_ = tokenEnd_ + delimiter_.size();
tokenEnd_ = view_.find(delimiter_, tokenStart_);
return *this;
}
bool operator!=(Iterator &other)
{
// We only check if both points to the end.
if (done_ && other.done_)
{
return false;
}
return true;
}
};
Iterator beginIter_;
StringSplit(std::string str, std::string delim)
: beginIter_{std::move(str), std::move(delim)}
{
}
Iterator begin()
{
return beginIter_;
}
Iterator end()
{
return Iterator{};
}
};
And example usage would be:
int main()
{
for (auto token : StringSplit{"<>foo<>bar<><>bar<><>baz<><>", "<>"})
{
std::cout << "TOKEN: '" << token << "'" << std::endl;
}
}
Which prints:
TOKEN: ''
TOKEN: 'foo'
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'baz'
TOKEN: ''
TOKEN: ''
It properly handles empty entries at the beginning and end of the string.
Here is an example of splitting a string with another string using Boost String Algorithms library and Boost Range library. The solution is inspired with (modest) suggestion from the the StringAlgo library documentation, see the Split section.
Below is a complete program with the split_with_string function as well as comprehensive test - try it with godbolt:
#include <iostream>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
std::vector<std::string> split_with_string(std::string_view s, std::string_view search)
{
if (search.empty()) return {std::string{s}};
std::vector<boost::iterator_range<std::string_view::iterator>> found;
boost::algorithm::ifind_all(found, s, search);
if (found.empty()) return {};
std::vector<std::string> parts;
parts.reserve(found.size() + 2); // a bit more
std::string_view::iterator part_begin = s.cbegin(), part_end;
for (auto& split_found : found)
{
// do not skip empty extracts
part_end = split_found.begin();
parts.emplace_back(part_begin, part_end);
part_begin = split_found.end();
}
if (part_end != s.end())
parts.emplace_back(part_begin, s.end());
return parts;
}
#define TEST(expr) std::cout << ((!(expr)) ? "FAIL" : "PASS") << ": " #expr "\t" << std::endl
int main()
{
auto s0 = split_with_string("adsf-+qwret-+nvfkbdsj", "");
TEST(s0.size() == 1);
TEST(s0.front() == "adsf-+qwret-+nvfkbdsj");
auto s1 = split_with_string("adsf-+qwret-+nvfkbdsj", "-+");
TEST(s1.size() == 3);
TEST(s1.front() == "adsf");
TEST(s1.back() == "nvfkbdsj");
auto s2 = split_with_string("-+adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s2.size() == 5);
TEST(s2.front() == "");
TEST(s2.back() == "");
auto s3 = split_with_string("-+adsf-+qwret-+nvfkbdsj", "-+");
TEST(s3.size() == 4);
TEST(s3.front() == "");
TEST(s3.back() == "nvfkbdsj");
auto s4 = split_with_string("adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s4.size() == 4);
TEST(s4.front() == "adsf");
TEST(s4.back() == "");
auto s5 = split_with_string("dbo.abc", "dbo.");
TEST(s5.size() == 2);
TEST(s5.front() == "");
TEST(s5.back() == "abc");
auto s6 = split_with_string("dbo.abc", ".");
TEST(s6.size() == 2);
TEST(s6.front() == "dbo");
TEST(s6.back() == "abc");
}
Tests output:
PASS: s0.size() == 1
PASS: s0.front() == "adsf-+qwret-+nvfkbdsj"
PASS: s1.size() == 3
PASS: s1.front() == "adsf"
PASS: s1.back() == "nvfkbdsj"
PASS: s2.size() == 5
PASS: s2.front() == ""
PASS: s2.back() == ""
PASS: s3.size() == 4
PASS: s3.front() == ""
PASS: s3.back() == "nvfkbdsj"
PASS: s4.size() == 4
PASS: s4.front() == "adsf"
PASS: s4.back() == ""
PASS: s5.size() == 2
PASS: s5.front() == ""
PASS: s5.back() == "abc"
PASS: s6.size() == 2
PASS: s6.front() == "dbo"
PASS: s6.back() == "abc"

How to split a line given a delimeter [duplicate]

I am parsing a string in C++ using the following:
using namespace std;
string parsed,input="text to be parsed";
stringstream input_stringstream(input);
if (getline(input_stringstream,parsed,' '))
{
// do some processing.
}
Parsing with a single char delimiter is fine. But what if I want to use a string as delimiter.
Example: I want to split:
scott>=tiger
with >= as delimiter so that I can get scott and tiger.
You can use the std::string::find() function to find the position of your string delimiter, then use std::string::substr() to get a token.
Example:
std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
The find(const string& str, size_t pos = 0) function returns the position of the first occurrence of str in the string, or npos if the string is not found.
The substr(size_t pos = 0, size_t n = npos) function returns a substring of the object, starting at position pos and of length npos.
If you have multiple delimiters, after you have extracted one token, you can remove it (delimiter included) to proceed with subsequent extractions (if you want to preserve the original string, just use s = s.substr(pos + delimiter.length());):
s.erase(0, s.find(delimiter) + delimiter.length());
This way you can easily loop to get each token.
Complete Example
std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";
size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
std::cout << token << std::endl;
s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;
Output:
scott
tiger
mushroom
For string delimiter
Split string based on a string delimiter. Such as splitting string "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih" based on string delimiter "-+", output will be {"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}
#include <iostream>
#include <sstream>
#include <vector>
// for string delimiter
std::vector<std::string> split(std::string s, std::string delimiter) {
size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token;
std::vector<std::string> res;
while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) {
token = s.substr (pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len;
res.push_back (token);
}
res.push_back (s.substr (pos_start));
return res;
}
int main() {
std::string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
std::string delimiter = "-+";
std::vector<std::string> v = split (str, delimiter);
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih
For single character delimiter
Split string based on a character delimiter. For example, splitting string "adsf+qwer+poui+fdgh" with delimiter "+" will output {"adsf", "qwer", "poui", "fdgh"}
#include <iostream>
#include <sstream>
#include <vector>
std::vector<std::string> split (const std::string &s, char delim) {
std::vector<std::string> result;
std::stringstream ss (s);
std::string item;
while (getline (ss, item, delim)) {
result.push_back (item);
}
return result;
}
int main() {
std::string str = "adsf+qwer+poui+fdgh";
std::vector<std::string> v = split (str, '+');
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwer
poui
fdgh
This method uses std::string::find without mutating the original string by remembering the beginning and end of the previous substring token.
#include <iostream>
#include <string>
int main()
{
std::string s = "scott>=tiger";
std::string delim = ">=";
auto start = 0U;
auto end = s.find(delim);
while (end != std::string::npos)
{
std::cout << s.substr(start, end - start) << std::endl;
start = end + delim.length();
end = s.find(delim, start);
}
std::cout << s.substr(start, end);
}
You can use next function to split string:
vector<string> split(const string& str, const string& delim)
{
vector<string> tokens;
size_t prev = 0, pos = 0;
do
{
pos = str.find(delim, prev);
if (pos == string::npos) pos = str.length();
string token = str.substr(prev, pos-prev);
if (!token.empty()) tokens.push_back(token);
prev = pos + delim.length();
}
while (pos < str.length() && prev < str.length());
return tokens;
}
You can also use regex for this:
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::regex regexz(regex_str);
std::vector<std::string> list(std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator());
return list;
}
which is equivalent to :
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::sregex_token_iterator token_iter(str.begin(), str.end(), regexz, -1);
std::sregex_token_iterator end;
std::vector<std::string> list;
while (token_iter != end)
{
list.emplace_back(*token_iter++);
}
return list;
}
and use it like this :
#include <iostream>
#include <string>
#include <regex>
std::vector<std::string> split(const std::string str,
const std::string regex_str) {
std::regex regexz(regex_str);
return {std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator()};
}
int main()
{
std::string input_str = "lets split this";
std::string regex_str = " ";
auto tokens = split(input_str, regex_str);
for (auto& item: tokens)
{
std::cout<<item <<std::endl;
}
}
play with it online!
you can simply use substrings, characters, etc like normal, or use actual regular expressions to do the splitting.
its also concise and C++11!
A way of doing it with C++20:
#include <iostream>
#include <ranges>
#include <string_view>
int main()
{
std::string hello = "text to be parsed";
auto split = hello
| std::ranges::views::split(' ')
| std::ranges::views::transform([](auto&& str) { return std::string_view(&*str.begin(), std::ranges::distance(str)); });
for (auto&& word : split)
{
std::cout << word << std::endl;
}
}
See:
https://stackoverflow.com/a/48403210/10771848
https://en.cppreference.com/w/cpp/ranges/split_view
This code splits lines from text, and add everyone into a vector.
vector<string> split(char *phrase, string delimiter){
vector<string> list;
string s = string(phrase);
size_t pos = 0;
string token;
while ((pos = s.find(delimiter)) != string::npos) {
token = s.substr(0, pos);
list.push_back(token);
s.erase(0, pos + delimiter.length());
}
list.push_back(s);
return list;
}
Called by:
vector<string> listFilesMax = split(buffer, "\n");
Answer is already there, but selected-answer uses erase function which is very costly, think of some very big string(in MBs). Therefore I use below function.
vector<string> split(const string& i_str, const string& i_delim)
{
vector<string> result;
size_t startIndex = 0;
for (size_t found = i_str.find(i_delim); found != string::npos; found = i_str.find(i_delim, startIndex))
{
result.emplace_back(i_str.begin()+startIndex, i_str.begin()+found);
startIndex = found + i_delim.size();
}
if (startIndex != i_str.size())
result.emplace_back(i_str.begin()+startIndex, i_str.end());
return result;
}
strtok allows you to pass in multiple chars as delimiters. I bet if you passed in ">=" your example string would be split correctly (even though the > and = are counted as individual delimiters).
EDIT if you don't want to use c_str() to convert from string to char*, you can use substr and find_first_of to tokenize.
string token, mystring("scott>=tiger");
while(token != mystring){
token = mystring.substr(0,mystring.find_first_of(">="));
mystring = mystring.substr(mystring.find_first_of(">=") + 1);
printf("%s ",token.c_str());
}
I would use boost::tokenizer. Here's documentation explaining how to make an appropriate tokenizer function: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm
Here's one that works for your case.
struct my_tokenizer_func
{
template<typename It>
bool operator()(It& next, It end, std::string & tok)
{
if (next == end)
return false;
char const * del = ">=";
auto pos = std::search(next, end, del, del + 2);
tok.assign(next, pos);
next = pos;
if (next != end)
std::advance(next, 2);
return true;
}
void reset() {}
};
int main()
{
std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
std::cout << i << '\n';
}
This should work perfectly for string (or single character) delimiters. Don't forget to include #include <sstream>.
std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+";
std::istringstream ss(input);
std::string token;
std::string::iterator it;
while(std::getline(ss, token, *(it = delimiter.begin()))) {
std::cout << token << std::endl; // Token is extracted using '='
it++;
// Skip the rest of delimiter if exists ",+"
while(it != delimiter.end() and ss.peek() == *(it)) {
it++; ss.get();
}
}
The first while loop extracts a token using the first character of the string delimiter. The second while loop skips the rest of the delimiter and stops at the beginning of the next token.
Here's my take on this. It handles the edge cases and takes an optional parameter to remove empty entries from the results.
bool endsWith(const std::string& s, const std::string& suffix)
{
return s.size() >= suffix.size() &&
s.substr(s.size() - suffix.size()) == suffix;
}
std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool removeEmptyEntries = false)
{
std::vector<std::string> tokens;
for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
{
size_t position = s.find(delimiter, start);
end = position != std::string::npos ? position : s.length();
std::string token = s.substr(start, end - start);
if (!removeEmptyEntries || !token.empty())
{
tokens.push_back(token);
}
}
if (!removeEmptyEntries &&
(s.empty() || endsWith(s, delimiter)))
{
tokens.push_back("");
}
return tokens;
}
Examples
split("a-b-c", "-"); // [3]("a","b","c")
split("a--c", "-"); // [3]("a","","c")
split("-b-", "-"); // [3]("","b","")
split("--c--", "-"); // [5]("","","c","","")
split("--c--", "-", true); // [1]("c")
split("a", "-"); // [1]("a")
split("", "-"); // [1]("")
split("", "-", true); // [0]()
A very simple/naive approach:
vector<string> words_seperate(string s){
vector<string> ans;
string w="";
for(auto i:s){
if(i==' '){
ans.push_back(w);
w="";
}
else{
w+=i;
}
}
ans.push_back(w);
return ans;
}
Or you can use boost library split function:
vector<string> result;
boost::split(result, input, boost::is_any_of("\t"));
Or You can try TOKEN or strtok:
char str[] = "DELIMIT-ME-C++";
char *token = strtok(str, "-");
while (token)
{
cout<<token;
token = strtok(NULL, "-");
}
Or You can do this:
char split_with=' ';
vector<string> words;
string token;
stringstream ss(our_string);
while(getline(ss , token , split_with)) words.push_back(token);
Just in case in the future, someone wants out of the box function of Vincenzo Pii 's answer
#include <vector>
#include <string>
std::vector<std::string> SplitString(
std::string str,
std::string delimeter)
{
std::vector<std::string> splittedStrings = {};
size_t pos = 0;
while ((pos = str.find(delimeter)) != std::string::npos)
{
std::string token = str.substr(0, pos);
if (token.length() > 0)
splittedStrings.push_back(token);
str.erase(0, pos + delimeter.length());
}
if (str.length() > 0)
splittedStrings.push_back(str);
return splittedStrings;
}
I also fixed some bugs so that the function won't return an empty string if there is a delimiter at the start or the end of the string
This is a complete method that splits the string on any delimiter and returns a vector of the chopped up strings.
It is an adaptation from the answer from ryanbwork. However, his check for: if(token != mystring) gives wrong results if you have repeating elements in your string. This is my solution to that problem.
vector<string> Split(string mystring, string delimiter)
{
vector<string> subStringList;
string token;
while (true)
{
size_t findfirst = mystring.find_first_of(delimiter);
if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
{
subStringList.push_back(mystring); //push back the final piece of mystring
return subStringList;
}
token = mystring.substr(0, mystring.find_first_of(delimiter));
mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
subStringList.push_back(token);
}
return subStringList;
}
Since this is the top-rated Stack Overflow Google search result for C++ split string or similar, I'll post a complete, copy/paste runnable example that shows both methods.
splitString uses stringstream (probably the better and easier option in most cases)
splitString2 uses find and substr (a more manual approach)
// SplitString.cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
// function prototypes
std::vector<std::string> splitString(const std::string& str, char delim);
std::vector<std::string> splitString2(const std::string& str, char delim);
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx);
int main(void)
{
// Test cases - all will pass
std::string str = "ab,cd,ef";
//std::string str = "abcdef";
//std::string str = "";
//std::string str = ",cd,ef";
//std::string str = "ab,cd,"; // behavior of splitString and splitString2 is different for this final case only, if this case matters to you choose which one you need as applicable
std::vector<std::string> tokens = splitString(str, ',');
std::cout << "tokens: " << "\n";
if (tokens.empty())
{
std::cout << "(tokens is empty)" << "\n";
}
else
{
for (auto& token : tokens)
{
if (token == "") std::cout << "(empty string)" << "\n";
else std::cout << token << "\n";
}
}
return 0;
}
std::vector<std::string> splitString(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
std::string currentToken;
std::stringstream ss(str);
while (std::getline(ss, currentToken, delim))
{
tokens.push_back(currentToken);
}
return tokens;
}
std::vector<std::string> splitString2(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
int leftIdx = 0;
int delimIdx = str.find(delim);
int rightIdx;
while (delimIdx != std::string::npos)
{
rightIdx = delimIdx - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
// prep for next time around
leftIdx = delimIdx + 1;
delimIdx = str.find(delim, delimIdx + 1);
}
rightIdx = str.size() - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
return tokens;
}
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx)
{
return str.substr(leftIdx, rightIdx - leftIdx + 1);
}
Yet another answer: Here I'm using find_first_not_of string function which returns the position of the first character that does not match any of the characters specified in the delim.
size_t find_first_not_of(const string& delim, size_t pos = 0) const noexcept;
Example:
int main()
{
size_t start = 0, end = 0;
std::string str = "scott>=tiger>=cat";
std::string delim = ">=";
while ((start = str.find_first_not_of(delim, end)) != std::string::npos)
{
end = str.find(delim, start); // finds the 'first' occurance from the 'start'
std::cout << str.substr(start, end - start)<<std::endl; // extract substring
}
return 0;
}
Output:
scott
tiger
cat
I make this solution. It is very simple, all the prints/values are in the loop (no need to check after the loop).
#include <iostream>
#include <string>
using std::cout;
using std::string;
int main() {
string s = "it-+is-+working!";
string d = "-+";
int firstFindI = 0;
int secendFindI = 0;
while (secendFindI != string::npos)
{
secendFindI = s.find(d, firstFindI);
cout << s.substr(firstFindI, secendFindI - firstFindI) << "\n"; // print sliced part
firstFindI = secendFindI + d.size(); // add to the search index
}
}
Thanks to #SteveWard for improving this answer.
This is similar to other answers but it's using string_view. So these are just views for the original string. Similar to the c++20 example. Though this would be a c++17 example. (edit to skip empty matches)
#include <algorithm>
#include <iostream>
#include <string_view>
#include <vector>
std::vector<std::string_view> split(std::string_view buffer,
const std::string_view delimeter = " ") {
std::vector<std::string_view> ret{};
std::decay_t<decltype(std::string_view::npos)> pos{};
while ((pos = buffer.find(delimeter)) != std::string_view::npos) {
const auto match = buffer.substr(0, pos);
if (!match.empty()) ret.push_back(match);
buffer = buffer.substr(pos + delimeter.size());
}
if (!buffer.empty()) ret.push_back(buffer);
return ret;
}
int main() {
const auto split_values = split("1 2 3 4 5 6 7 8 9 10 ");
std::for_each(split_values.begin(), split_values.end(),
[](const auto& str) { std::cout << str << '\n'; });
return split_values.size();
}
If you do not want to modify the string (as in the answer by Vincenzo Pii) and want to output the last token as well, you may want to use this approach:
inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
std::vector<std::string> ret;
size_t start = 0;
size_t end = 0;
size_t len = 0;
std::string token;
do{ end = s.find(delimiter,start);
len = end - start;
token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
std::cout << token << std::endl;
}while ( end != std::string::npos );
return ret;
}
Here's a concise split function. I decided to have back to back delimiters return as an empty string but you could easily check that if the substring is empty and not add it to the vector if it is.
#include <vector>
#include <string>
using namespace std;
vector<string> split(string to_split, string delimiter) {
size_t pos = 0;
vector<string> matches{};
do {
pos = to_split.find(delimiter);
int change_end;
if (pos == string::npos) {
pos = to_split.length() - 1;
change_end = 1;
}
else {
change_end = 0;
}
matches.push_back(to_split.substr(0, pos+change_end));
to_split.erase(0, pos+1);
}
while (!to_split.empty());
return matches;
}
This method use string find and string substr
vector<string> split(const string& str,const string delim){
vector<string> vtokens;
size_t start = 0;
size_t end = 0;
while((end = str.find(delim,start))!=string::npos){
vtokens.push_back(str.substr(start,end-start));
start = end +1;
}
vtokens.push_back(str.substr(start));
return vtokens;
}
#include<iostream>
#include<algorithm>
using namespace std;
int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}
void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}
int main(){
string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);
for(int i=0;i<x;i++)
cout<<res[i]<<endl;
return 0;
}
P.S: Works only if the lengths of the strings after splitting are equal
std::vector<std::string> parse(std::string str,std::string delim){
std::vector<std::string> tokens;
char *str_c = strdup(str.c_str());
char* token = NULL;
token = strtok(str_c, delim.c_str());
while (token != NULL) {
tokens.push_back(std::string(token));
token = strtok(NULL, delim.c_str());
}
delete[] str_c;
return tokens;
}
Function:
std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
std::vector<std::string> vRet;
size_t nPos = 0;
size_t nLen = sWhat.length();
size_t nDelimLen = sDelim.length();
while (nPos < nLen) {
std::size_t nFoundPos = sWhat.find(sDelim, nPos);
if (nFoundPos != std::string::npos) {
std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
vRet.push_back(sToken);
nPos = nFoundPos + nDelimLen;
if (nFoundPos + nDelimLen == nLen) { // last delimiter
vRet.push_back("");
}
} else {
std::string sToken = sWhat.substr(nPos, nLen - nPos);
vRet.push_back(sToken);
break;
}
}
return vRet;
}
Unit-tests:
bool UnitTestSplit::run() {
bool bTestSuccess = true;
struct LTest {
LTest(
const std::string &sStr,
const std::string &sDelim,
const std::vector<std::string> &vExpectedVector
) {
this->sStr = sStr;
this->sDelim = sDelim;
this->vExpectedVector = vExpectedVector;
};
std::string sStr;
std::string sDelim;
std::vector<std::string> vExpectedVector;
};
std::vector<LTest> tests;
tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));
for (int i = 0; i < tests.size(); i++) {
LTest test = tests[i];
std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
for (int n = 0; n < nMin; n++) {
compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
}
}
return bTestSuccess;
}
i use pointer arithmetic. inner while for string delimeter if you satify with char delim just remove inner while simply. i hope it is correct. if you notice any mistake or improve please leave the comment.
std::vector<std::string> split(std::string s, std::string delim)
{
char *p = &s[0];
char *d = &delim[0];
std::vector<std::string> res = {""};
do
{
bool is_delim = true;
char *pp = p;
char *dd = d;
while (*dd && is_delim == true)
if (*pp++ != *dd++)
is_delim = false;
if (is_delim)
{
p = pp - 1;
res.push_back("");
}
else
*(res.rbegin()) += *p;
} while (*p++);
return res;
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.push_back(std::forward<T>(t)), void()) {
c.push_back(std::forward<T>(t));
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.insert(std::forward<T>(t)), void()) {
c.insert(std::forward<T>(t));
}
template<typename Container>
Container splitR(const std::string& input, const std::string& delims) {
Container out;
size_t delims_len = delims.size();
auto begIdx = 0u;
auto endIdx = input.find(delims, begIdx);
if (endIdx == std::string::npos && input.size() != 0u) {
insert_in_container(out, input);
}
else {
size_t w = 0;
while (endIdx != std::string::npos) {
w = endIdx - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
begIdx = endIdx + delims_len;
endIdx = input.find(delims, begIdx);
}
w = input.length() - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
}
return out;
}
A simpler solution would be -
You can use strtok to delimit on the basis of multichar delimiter.
Remember to use strdup so that the orignal string isn't mutated.
#include <stdio.h>
#include <string.h>
const char* str = "scott>=tiger";
char *token = strtok(strdup(str), ">=");
while (token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, ">=");
}
I looked through the answers and haven't seen an iterator based approach that can be fed into a range loop, so I made one.
This uses C++17 string_views so it shouldn't allocate copies of the string.
struct StringSplit
{
struct Iterator
{
size_t tokenStart_ = 0;
size_t tokenEnd_ = 0;
std::string str_;
std::string_view view_;
std::string delimiter_;
bool done_ = false;
Iterator()
{
// End iterator.
done_ = true;
}
Iterator(std::string str, std::string delimiter)
: str_{std::move(str)}, view_{str_}, delimiter_{
std::move(delimiter)}
{
tokenEnd_ = view_.find(delimiter_, tokenStart_);
}
std::string_view operator*()
{
return view_.substr(tokenStart_, tokenEnd_ - tokenStart_);
}
Iterator &operator++()
{
if (tokenEnd_ == std::string::npos)
{
done_ = true;
return *this;
}
tokenStart_ = tokenEnd_ + delimiter_.size();
tokenEnd_ = view_.find(delimiter_, tokenStart_);
return *this;
}
bool operator!=(Iterator &other)
{
// We only check if both points to the end.
if (done_ && other.done_)
{
return false;
}
return true;
}
};
Iterator beginIter_;
StringSplit(std::string str, std::string delim)
: beginIter_{std::move(str), std::move(delim)}
{
}
Iterator begin()
{
return beginIter_;
}
Iterator end()
{
return Iterator{};
}
};
And example usage would be:
int main()
{
for (auto token : StringSplit{"<>foo<>bar<><>bar<><>baz<><>", "<>"})
{
std::cout << "TOKEN: '" << token << "'" << std::endl;
}
}
Which prints:
TOKEN: ''
TOKEN: 'foo'
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'baz'
TOKEN: ''
TOKEN: ''
It properly handles empty entries at the beginning and end of the string.
Here is an example of splitting a string with another string using Boost String Algorithms library and Boost Range library. The solution is inspired with (modest) suggestion from the the StringAlgo library documentation, see the Split section.
Below is a complete program with the split_with_string function as well as comprehensive test - try it with godbolt:
#include <iostream>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
std::vector<std::string> split_with_string(std::string_view s, std::string_view search)
{
if (search.empty()) return {std::string{s}};
std::vector<boost::iterator_range<std::string_view::iterator>> found;
boost::algorithm::ifind_all(found, s, search);
if (found.empty()) return {};
std::vector<std::string> parts;
parts.reserve(found.size() + 2); // a bit more
std::string_view::iterator part_begin = s.cbegin(), part_end;
for (auto& split_found : found)
{
// do not skip empty extracts
part_end = split_found.begin();
parts.emplace_back(part_begin, part_end);
part_begin = split_found.end();
}
if (part_end != s.end())
parts.emplace_back(part_begin, s.end());
return parts;
}
#define TEST(expr) std::cout << ((!(expr)) ? "FAIL" : "PASS") << ": " #expr "\t" << std::endl
int main()
{
auto s0 = split_with_string("adsf-+qwret-+nvfkbdsj", "");
TEST(s0.size() == 1);
TEST(s0.front() == "adsf-+qwret-+nvfkbdsj");
auto s1 = split_with_string("adsf-+qwret-+nvfkbdsj", "-+");
TEST(s1.size() == 3);
TEST(s1.front() == "adsf");
TEST(s1.back() == "nvfkbdsj");
auto s2 = split_with_string("-+adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s2.size() == 5);
TEST(s2.front() == "");
TEST(s2.back() == "");
auto s3 = split_with_string("-+adsf-+qwret-+nvfkbdsj", "-+");
TEST(s3.size() == 4);
TEST(s3.front() == "");
TEST(s3.back() == "nvfkbdsj");
auto s4 = split_with_string("adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s4.size() == 4);
TEST(s4.front() == "adsf");
TEST(s4.back() == "");
auto s5 = split_with_string("dbo.abc", "dbo.");
TEST(s5.size() == 2);
TEST(s5.front() == "");
TEST(s5.back() == "abc");
auto s6 = split_with_string("dbo.abc", ".");
TEST(s6.size() == 2);
TEST(s6.front() == "dbo");
TEST(s6.back() == "abc");
}
Tests output:
PASS: s0.size() == 1
PASS: s0.front() == "adsf-+qwret-+nvfkbdsj"
PASS: s1.size() == 3
PASS: s1.front() == "adsf"
PASS: s1.back() == "nvfkbdsj"
PASS: s2.size() == 5
PASS: s2.front() == ""
PASS: s2.back() == ""
PASS: s3.size() == 4
PASS: s3.front() == ""
PASS: s3.back() == "nvfkbdsj"
PASS: s4.size() == 4
PASS: s4.front() == "adsf"
PASS: s4.back() == ""
PASS: s5.size() == 2
PASS: s5.front() == ""
PASS: s5.back() == "abc"
PASS: s6.size() == 2
PASS: s6.front() == "dbo"
PASS: s6.back() == "abc"

Deleting two specific Characters

So I have a little problem, I want to achieve this in C++, but I don't know how to do it:
Given is a string containing random numbers, symbols, and letters:
std::string = "1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9^1dkkns";
Now I'm trying to find all ^ characters, and if those are followed by a number between 0 and 9, delete the ^ and the number, so:
"^1ghhu^7dndn^g"
becomes:
"ghhudndn^g"
I know how to find and replace/erase chars from a string, but I don't know how to check if it's followed by a number in a not hard coded way. Any help is appreciated.
std::string s = "^1ghhu^7dndn^g";
for (int i = 0; i < s.length() - 1; ++i)
{
if (s[i] == '^' && std::isdigit(s[i + 1]))
{
s.erase(i, 2);
--i;
}
}
This needs these includes:
#include <string>
#include <cctype>
I would do it this way:
#include <iostream>
#include <string>
#include <utility>
#include <iterator>
template<class Iter, class OutIter>
OutIter remove_escaped_numbers(Iter first, Iter last, OutIter out) {
for ( ; first != last ; )
{
auto c = *first++;
if (c == '^' && first != last)
{
c = *first++;
if (std::isdigit(c))
continue;
else {
*out++ = '^';
*out++ = c;
}
}
else {
*out++ = c;
}
}
return out;
}
int main()
{
using namespace std::literals;
auto input = "^1ghhu^7dndn^g"s;
auto output = std::string{};
remove_escaped_numbers(input.begin(), input.end(), std::back_inserter(output));
std::cout << output << std::endl;
}
or this way:
#include <iostream>
#include <regex>
int main()
{
using namespace std::literals;
auto input = "^1ghhu^7dndn^g"s;
static const auto repl = std::regex { R"___(\^\d)___" };
auto output = std::regex_replace(input, repl, "");
std::cout << output << std::endl;
}
A solution using std::stringstream, and returning the input string cleared of caret-digit's.
#include <iostream>
#include <sstream>
#include <cctype>
int t404()
{
std::stringstream ss;
std::string inStr("1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9^1dkkns");
for (size_t i = 0; i<inStr.size(); ++i)
{
if(('^' == inStr[i]) && isdigit(inStr[i+1]))
{
i += 1; // skip over caret followed by single digit
}
else
{
ss << inStr[i];
}
}
std::cout << inStr << std::endl; // compare input
std::cout << ss.str() << std::endl; // to results
return 0;
}
Output:
1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9^1dkkns
1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9dkkns
you can simply loop over the string and copy it while skipping the undesired chars. Here is a possible function to do it:
std::string filterString (std::string& s) {
std::string result = "";
std::string::iterator it = s.begin();
char c;
while (it != s.end()) {
if (*it == '^' && it != s.end() && (it + 1) != s.end()) {
c = *(it + 1);
if(c >= '0' && c <= '9') {
it += 2;
continue;
}
}
result.push_back(*it);
++ it;
}
return result;
}
A robust solution would be to use the regex library that C++11 brings in.
std::string input ("1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9^1dkkns");
std::regex rx ("[\\^][\\d]{1}"); // "[\^][\d]{1}"
std::cout << std::regex_replace(input,rx,"woot");
>> 1653gbdtsr362g2v3f3t52bv^hdtvsbjj;hdfuue,9wootdkkns
This locates a "^" character ([\^]) followed by 1 ({1}) digit ([\d]) and replaces all occurances with "woot".
Hope this code can solve your problem:
#include <iostream>
#include <string>
int main()
{
std::string str = "^1ghhu^7dndn^g";
std::string::iterator first, last;
for ( std::string::iterator it=str.begin(); it!=str.end(); ++it)
{
if(*it == '^')
{
first = it;
it++;
while(isdigit(*it))
{
it++;
}
last = it - 1;
if(first != last)
{
if((last + 1) != str.end())
{
str.erase(first, last + 1);
}
else
{
str.erase(first, str.end());
break;
}
}
}
}
std::cout<< str << std::endl;
return 0;
}
The output:
$ ./erase
ghhudndn^g

Split a string on multiple multi-char delimeters in C++

How can I split a string on multiple multi-character delimiters?
I want a function like vector<string> split_string(string input, vector<string> delims)
For example, split_string("foo+bar := baz",{"+"," ",":="}) = {"foo","+","bar"," "," ",":="," ","baz"}
My cut at the same. I chose to go with divide and conquer. It is not fast. It is not efficient. But it is simple.
Unfortunately it didn't work in this case because we are preserving the delimiters in the output. Dividing allowed later delimiters to split previously found delimiters.
Eg:
Source :=foo+bar . :=baz+quaax:= C++
Delims [+][ ][:=][:]
Result [:][=][foo][+][bar][ ][ ][.][ ][ ][ ][:][=][baz][+][quaax][:][=][ ][ ][C][+][+]
Yuck.
Finally settled on a similar approach to jafar's and added it to my support library to try out in a job I'm working on to replace the divide and conquer approach because it does look to be faster. Wouldn't have bothered posting this, but Jafar's is a bit over complicated for my tastes. Haven't done any profiling so his may be faster.
#include <iostream>
#include <vector>
// easy vector output
template<class TYPE>
std::ostream & operator<<(std::ostream & out,
const std::vector<TYPE> & in)
{
for (const TYPE &val: in)
{
out << "["<< val << "]";
}
return out;
}
// find the first of many string delimiters
size_t multifind(size_t start,
const std::string & source,
const std::vector<std::string> &delims,
size_t & delfound)
{
size_t lowest = std::string::npos;
for (size_t i = 0; i < delims.size(); i++)
{
size_t pos = source.find(delims[i], start);
if (pos == start)
{
lowest = pos;
delfound = i;
break;
}
else if (pos < lowest)
{
lowest = pos;
delfound = i;
}
}
return lowest;
}
// do the grunt work
std::vector<std::string> splitString(const std::string &source,
const std::vector<std::string> &delims)
{
std::vector<std::string> tokens;
size_t current = 0;
size_t delfound;
size_t next = multifind(current,
source,
delims,
delfound);
while(next != std::string::npos)
{
if (current < next)
{
tokens.push_back(source.substr(current, next - current));
}
tokens.push_back(delims[delfound]);
current = next + delims[delfound].length();
next = multifind(current,
source,
delims,
delfound);
}
if (current < source.length())
{
tokens.push_back(source.substr(current, std::string::npos));
}
return tokens;
}
void test(const std::string &source,
const std::vector<std::string> &delims)
{
std::cout << "Source " << source << std::endl;
std::cout << "Delims " << delims << std::endl;
std::cout << "Result " << splitString(source, delims) << std::endl << std::endl;
}
int main()
{
test(":=foo+bar . :=baz+quaax:= C++", { " ",":=","+" });
test(":=foo+bar . :=baz+quaax:= C++", { ":=","+"," " });
test(":=foo+bar . :=baz+quaax:= C++", { "+"," ",":=" });
test(":=foo+bar . :=baz+quaax:= C++", { "+"," ",":=",":" });
test(":=foo+bar . :=baz+quaax:= C++", { ":"," ",":=","+" });
test("foo+bar . :=baz+quaax:= C++lalala", { "+"," ",":=",":" });
}
Try this
#include <iostream>
#include <string>
#include <vector>
#include <map>
std::vector<std::string> splitString(std::string input, std::vector<std::string> delimeters);
std::string findFirstOf(std::string input, std::vector<std::string> del);
int main()
{
std::vector<std::string> words = splitString(":=foo+bar :=baz+quaax", { " ",":=","+" });
for (std::string str : words)
std::cout << str << ",";
std::cout << std::endl;
system("pause");
}
std::vector<std::string> splitString(std::string input, std::vector<std::string> delimeters)
{
std::vector<std::string> result;
size_t pos = 0;
std::string token;
std::string delimeter = findFirstOf(input, delimeters);
while(delimeter != "")
{
if ((pos = input.find(delimeter)) != std::string::npos)
{
token = input.substr(0, pos);
result.push_back(token);
result.push_back(delimeter);
input.erase(0, pos + delimeter.length());
}
delimeter = findFirstOf(input, delimeters);
}
result.push_back(input);
return result;
}
//find the first delimeter in the string
std::string findFirstOf(std::string input, std::vector<std::string> del)
{
//get a map of delimeter and position of delimeter
size_t pos;
std::map<std::string, size_t> m;
for (int i = 0; i < del.size(); i++)
{
pos = input.find(del[i]);
if (pos != std::string::npos)
m[del[i]] = pos;
}
//find the smallest position of all delimeters i.e, find the smallest value in the map
if (m.size() == 0)
return "";
size_t v = m.begin()->second;
std::string k = m.begin()->first;
for (auto it = m.begin(); it != m.end(); it++)
{
if (it->second < v)
{
v = it->second;
k = it->first;
}
}
return k;
}
output: ,:=,foo,+,bar, ,,:=,baz,+,quaax,.

Parse (split) a string in C++ using string delimiter (standard C++)

I am parsing a string in C++ using the following:
using namespace std;
string parsed,input="text to be parsed";
stringstream input_stringstream(input);
if (getline(input_stringstream,parsed,' '))
{
// do some processing.
}
Parsing with a single char delimiter is fine. But what if I want to use a string as delimiter.
Example: I want to split:
scott>=tiger
with >= as delimiter so that I can get scott and tiger.
You can use the std::string::find() function to find the position of your string delimiter, then use std::string::substr() to get a token.
Example:
std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
The find(const string& str, size_t pos = 0) function returns the position of the first occurrence of str in the string, or npos if the string is not found.
The substr(size_t pos = 0, size_t n = npos) function returns a substring of the object, starting at position pos and of length npos.
If you have multiple delimiters, after you have extracted one token, you can remove it (delimiter included) to proceed with subsequent extractions (if you want to preserve the original string, just use s = s.substr(pos + delimiter.length());):
s.erase(0, s.find(delimiter) + delimiter.length());
This way you can easily loop to get each token.
Complete Example
std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";
size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
std::cout << token << std::endl;
s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;
Output:
scott
tiger
mushroom
For string delimiter
Split string based on a string delimiter. Such as splitting string "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih" based on string delimiter "-+", output will be {"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}
#include <iostream>
#include <sstream>
#include <vector>
// for string delimiter
std::vector<std::string> split(std::string s, std::string delimiter) {
size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token;
std::vector<std::string> res;
while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) {
token = s.substr (pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len;
res.push_back (token);
}
res.push_back (s.substr (pos_start));
return res;
}
int main() {
std::string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
std::string delimiter = "-+";
std::vector<std::string> v = split (str, delimiter);
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih
For single character delimiter
Split string based on a character delimiter. For example, splitting string "adsf+qwer+poui+fdgh" with delimiter "+" will output {"adsf", "qwer", "poui", "fdgh"}
#include <iostream>
#include <sstream>
#include <vector>
std::vector<std::string> split (const std::string &s, char delim) {
std::vector<std::string> result;
std::stringstream ss (s);
std::string item;
while (getline (ss, item, delim)) {
result.push_back (item);
}
return result;
}
int main() {
std::string str = "adsf+qwer+poui+fdgh";
std::vector<std::string> v = split (str, '+');
for (auto i : v) cout << i << endl;
return 0;
}
**Output**
adsf
qwer
poui
fdgh
This method uses std::string::find without mutating the original string by remembering the beginning and end of the previous substring token.
#include <iostream>
#include <string>
int main()
{
std::string s = "scott>=tiger";
std::string delim = ">=";
auto start = 0U;
auto end = s.find(delim);
while (end != std::string::npos)
{
std::cout << s.substr(start, end - start) << std::endl;
start = end + delim.length();
end = s.find(delim, start);
}
std::cout << s.substr(start, end);
}
You can use next function to split string:
vector<string> split(const string& str, const string& delim)
{
vector<string> tokens;
size_t prev = 0, pos = 0;
do
{
pos = str.find(delim, prev);
if (pos == string::npos) pos = str.length();
string token = str.substr(prev, pos-prev);
if (!token.empty()) tokens.push_back(token);
prev = pos + delim.length();
}
while (pos < str.length() && prev < str.length());
return tokens;
}
You can also use regex for this:
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::regex regexz(regex_str);
std::vector<std::string> list(std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator());
return list;
}
which is equivalent to :
std::vector<std::string> split(const std::string str, const std::string regex_str)
{
std::sregex_token_iterator token_iter(str.begin(), str.end(), regexz, -1);
std::sregex_token_iterator end;
std::vector<std::string> list;
while (token_iter != end)
{
list.emplace_back(*token_iter++);
}
return list;
}
and use it like this :
#include <iostream>
#include <string>
#include <regex>
std::vector<std::string> split(const std::string str,
const std::string regex_str) {
std::regex regexz(regex_str);
return {std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
std::sregex_token_iterator()};
}
int main()
{
std::string input_str = "lets split this";
std::string regex_str = " ";
auto tokens = split(input_str, regex_str);
for (auto& item: tokens)
{
std::cout<<item <<std::endl;
}
}
play with it online!
you can simply use substrings, characters, etc like normal, or use actual regular expressions to do the splitting.
its also concise and C++11!
A way of doing it with C++20:
#include <iostream>
#include <ranges>
#include <string_view>
int main()
{
std::string hello = "text to be parsed";
auto split = hello
| std::ranges::views::split(' ')
| std::ranges::views::transform([](auto&& str) { return std::string_view(&*str.begin(), std::ranges::distance(str)); });
for (auto&& word : split)
{
std::cout << word << std::endl;
}
}
See:
https://stackoverflow.com/a/48403210/10771848
https://en.cppreference.com/w/cpp/ranges/split_view
This code splits lines from text, and add everyone into a vector.
vector<string> split(char *phrase, string delimiter){
vector<string> list;
string s = string(phrase);
size_t pos = 0;
string token;
while ((pos = s.find(delimiter)) != string::npos) {
token = s.substr(0, pos);
list.push_back(token);
s.erase(0, pos + delimiter.length());
}
list.push_back(s);
return list;
}
Called by:
vector<string> listFilesMax = split(buffer, "\n");
Answer is already there, but selected-answer uses erase function which is very costly, think of some very big string(in MBs). Therefore I use below function.
vector<string> split(const string& i_str, const string& i_delim)
{
vector<string> result;
size_t startIndex = 0;
for (size_t found = i_str.find(i_delim); found != string::npos; found = i_str.find(i_delim, startIndex))
{
result.emplace_back(i_str.begin()+startIndex, i_str.begin()+found);
startIndex = found + i_delim.size();
}
if (startIndex != i_str.size())
result.emplace_back(i_str.begin()+startIndex, i_str.end());
return result;
}
strtok allows you to pass in multiple chars as delimiters. I bet if you passed in ">=" your example string would be split correctly (even though the > and = are counted as individual delimiters).
EDIT if you don't want to use c_str() to convert from string to char*, you can use substr and find_first_of to tokenize.
string token, mystring("scott>=tiger");
while(token != mystring){
token = mystring.substr(0,mystring.find_first_of(">="));
mystring = mystring.substr(mystring.find_first_of(">=") + 1);
printf("%s ",token.c_str());
}
I would use boost::tokenizer. Here's documentation explaining how to make an appropriate tokenizer function: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm
Here's one that works for your case.
struct my_tokenizer_func
{
template<typename It>
bool operator()(It& next, It end, std::string & tok)
{
if (next == end)
return false;
char const * del = ">=";
auto pos = std::search(next, end, del, del + 2);
tok.assign(next, pos);
next = pos;
if (next != end)
std::advance(next, 2);
return true;
}
void reset() {}
};
int main()
{
std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
std::cout << i << '\n';
}
This should work perfectly for string (or single character) delimiters. Don't forget to include #include <sstream>.
std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+";
std::istringstream ss(input);
std::string token;
std::string::iterator it;
while(std::getline(ss, token, *(it = delimiter.begin()))) {
std::cout << token << std::endl; // Token is extracted using '='
it++;
// Skip the rest of delimiter if exists ",+"
while(it != delimiter.end() and ss.peek() == *(it)) {
it++; ss.get();
}
}
The first while loop extracts a token using the first character of the string delimiter. The second while loop skips the rest of the delimiter and stops at the beginning of the next token.
Here's my take on this. It handles the edge cases and takes an optional parameter to remove empty entries from the results.
bool endsWith(const std::string& s, const std::string& suffix)
{
return s.size() >= suffix.size() &&
s.substr(s.size() - suffix.size()) == suffix;
}
std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool removeEmptyEntries = false)
{
std::vector<std::string> tokens;
for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
{
size_t position = s.find(delimiter, start);
end = position != std::string::npos ? position : s.length();
std::string token = s.substr(start, end - start);
if (!removeEmptyEntries || !token.empty())
{
tokens.push_back(token);
}
}
if (!removeEmptyEntries &&
(s.empty() || endsWith(s, delimiter)))
{
tokens.push_back("");
}
return tokens;
}
Examples
split("a-b-c", "-"); // [3]("a","b","c")
split("a--c", "-"); // [3]("a","","c")
split("-b-", "-"); // [3]("","b","")
split("--c--", "-"); // [5]("","","c","","")
split("--c--", "-", true); // [1]("c")
split("a", "-"); // [1]("a")
split("", "-"); // [1]("")
split("", "-", true); // [0]()
A very simple/naive approach:
vector<string> words_seperate(string s){
vector<string> ans;
string w="";
for(auto i:s){
if(i==' '){
ans.push_back(w);
w="";
}
else{
w+=i;
}
}
ans.push_back(w);
return ans;
}
Or you can use boost library split function:
vector<string> result;
boost::split(result, input, boost::is_any_of("\t"));
Or You can try TOKEN or strtok:
char str[] = "DELIMIT-ME-C++";
char *token = strtok(str, "-");
while (token)
{
cout<<token;
token = strtok(NULL, "-");
}
Or You can do this:
char split_with=' ';
vector<string> words;
string token;
stringstream ss(our_string);
while(getline(ss , token , split_with)) words.push_back(token);
Just in case in the future, someone wants out of the box function of Vincenzo Pii 's answer
#include <vector>
#include <string>
std::vector<std::string> SplitString(
std::string str,
std::string delimeter)
{
std::vector<std::string> splittedStrings = {};
size_t pos = 0;
while ((pos = str.find(delimeter)) != std::string::npos)
{
std::string token = str.substr(0, pos);
if (token.length() > 0)
splittedStrings.push_back(token);
str.erase(0, pos + delimeter.length());
}
if (str.length() > 0)
splittedStrings.push_back(str);
return splittedStrings;
}
I also fixed some bugs so that the function won't return an empty string if there is a delimiter at the start or the end of the string
This is a complete method that splits the string on any delimiter and returns a vector of the chopped up strings.
It is an adaptation from the answer from ryanbwork. However, his check for: if(token != mystring) gives wrong results if you have repeating elements in your string. This is my solution to that problem.
vector<string> Split(string mystring, string delimiter)
{
vector<string> subStringList;
string token;
while (true)
{
size_t findfirst = mystring.find_first_of(delimiter);
if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
{
subStringList.push_back(mystring); //push back the final piece of mystring
return subStringList;
}
token = mystring.substr(0, mystring.find_first_of(delimiter));
mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
subStringList.push_back(token);
}
return subStringList;
}
Since this is the top-rated Stack Overflow Google search result for C++ split string or similar, I'll post a complete, copy/paste runnable example that shows both methods.
splitString uses stringstream (probably the better and easier option in most cases)
splitString2 uses find and substr (a more manual approach)
// SplitString.cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
// function prototypes
std::vector<std::string> splitString(const std::string& str, char delim);
std::vector<std::string> splitString2(const std::string& str, char delim);
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx);
int main(void)
{
// Test cases - all will pass
std::string str = "ab,cd,ef";
//std::string str = "abcdef";
//std::string str = "";
//std::string str = ",cd,ef";
//std::string str = "ab,cd,"; // behavior of splitString and splitString2 is different for this final case only, if this case matters to you choose which one you need as applicable
std::vector<std::string> tokens = splitString(str, ',');
std::cout << "tokens: " << "\n";
if (tokens.empty())
{
std::cout << "(tokens is empty)" << "\n";
}
else
{
for (auto& token : tokens)
{
if (token == "") std::cout << "(empty string)" << "\n";
else std::cout << token << "\n";
}
}
return 0;
}
std::vector<std::string> splitString(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
std::string currentToken;
std::stringstream ss(str);
while (std::getline(ss, currentToken, delim))
{
tokens.push_back(currentToken);
}
return tokens;
}
std::vector<std::string> splitString2(const std::string& str, char delim)
{
std::vector<std::string> tokens;
if (str == "") return tokens;
int leftIdx = 0;
int delimIdx = str.find(delim);
int rightIdx;
while (delimIdx != std::string::npos)
{
rightIdx = delimIdx - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
// prep for next time around
leftIdx = delimIdx + 1;
delimIdx = str.find(delim, delimIdx + 1);
}
rightIdx = str.size() - 1;
std::string token = getSubstring(str, leftIdx, rightIdx);
tokens.push_back(token);
return tokens;
}
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx)
{
return str.substr(leftIdx, rightIdx - leftIdx + 1);
}
Yet another answer: Here I'm using find_first_not_of string function which returns the position of the first character that does not match any of the characters specified in the delim.
size_t find_first_not_of(const string& delim, size_t pos = 0) const noexcept;
Example:
int main()
{
size_t start = 0, end = 0;
std::string str = "scott>=tiger>=cat";
std::string delim = ">=";
while ((start = str.find_first_not_of(delim, end)) != std::string::npos)
{
end = str.find(delim, start); // finds the 'first' occurance from the 'start'
std::cout << str.substr(start, end - start)<<std::endl; // extract substring
}
return 0;
}
Output:
scott
tiger
cat
I make this solution. It is very simple, all the prints/values are in the loop (no need to check after the loop).
#include <iostream>
#include <string>
using std::cout;
using std::string;
int main() {
string s = "it-+is-+working!";
string d = "-+";
int firstFindI = 0;
int secendFindI = 0;
while (secendFindI != string::npos)
{
secendFindI = s.find(d, firstFindI);
cout << s.substr(firstFindI, secendFindI - firstFindI) << "\n"; // print sliced part
firstFindI = secendFindI + d.size(); // add to the search index
}
}
Thanks to #SteveWard for improving this answer.
This is similar to other answers but it's using string_view. So these are just views for the original string. Similar to the c++20 example. Though this would be a c++17 example. (edit to skip empty matches)
#include <algorithm>
#include <iostream>
#include <string_view>
#include <vector>
std::vector<std::string_view> split(std::string_view buffer,
const std::string_view delimeter = " ") {
std::vector<std::string_view> ret{};
std::decay_t<decltype(std::string_view::npos)> pos{};
while ((pos = buffer.find(delimeter)) != std::string_view::npos) {
const auto match = buffer.substr(0, pos);
if (!match.empty()) ret.push_back(match);
buffer = buffer.substr(pos + delimeter.size());
}
if (!buffer.empty()) ret.push_back(buffer);
return ret;
}
int main() {
const auto split_values = split("1 2 3 4 5 6 7 8 9 10 ");
std::for_each(split_values.begin(), split_values.end(),
[](const auto& str) { std::cout << str << '\n'; });
return split_values.size();
}
If you do not want to modify the string (as in the answer by Vincenzo Pii) and want to output the last token as well, you may want to use this approach:
inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
std::vector<std::string> ret;
size_t start = 0;
size_t end = 0;
size_t len = 0;
std::string token;
do{ end = s.find(delimiter,start);
len = end - start;
token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
std::cout << token << std::endl;
}while ( end != std::string::npos );
return ret;
}
Here's a concise split function. I decided to have back to back delimiters return as an empty string but you could easily check that if the substring is empty and not add it to the vector if it is.
#include <vector>
#include <string>
using namespace std;
vector<string> split(string to_split, string delimiter) {
size_t pos = 0;
vector<string> matches{};
do {
pos = to_split.find(delimiter);
int change_end;
if (pos == string::npos) {
pos = to_split.length() - 1;
change_end = 1;
}
else {
change_end = 0;
}
matches.push_back(to_split.substr(0, pos+change_end));
to_split.erase(0, pos+1);
}
while (!to_split.empty());
return matches;
}
This method use string find and string substr
vector<string> split(const string& str,const string delim){
vector<string> vtokens;
size_t start = 0;
size_t end = 0;
while((end = str.find(delim,start))!=string::npos){
vtokens.push_back(str.substr(start,end-start));
start = end +1;
}
vtokens.push_back(str.substr(start));
return vtokens;
}
#include<iostream>
#include<algorithm>
using namespace std;
int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}
void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}
int main(){
string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);
for(int i=0;i<x;i++)
cout<<res[i]<<endl;
return 0;
}
P.S: Works only if the lengths of the strings after splitting are equal
std::vector<std::string> parse(std::string str,std::string delim){
std::vector<std::string> tokens;
char *str_c = strdup(str.c_str());
char* token = NULL;
token = strtok(str_c, delim.c_str());
while (token != NULL) {
tokens.push_back(std::string(token));
token = strtok(NULL, delim.c_str());
}
delete[] str_c;
return tokens;
}
Function:
std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
std::vector<std::string> vRet;
size_t nPos = 0;
size_t nLen = sWhat.length();
size_t nDelimLen = sDelim.length();
while (nPos < nLen) {
std::size_t nFoundPos = sWhat.find(sDelim, nPos);
if (nFoundPos != std::string::npos) {
std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
vRet.push_back(sToken);
nPos = nFoundPos + nDelimLen;
if (nFoundPos + nDelimLen == nLen) { // last delimiter
vRet.push_back("");
}
} else {
std::string sToken = sWhat.substr(nPos, nLen - nPos);
vRet.push_back(sToken);
break;
}
}
return vRet;
}
Unit-tests:
bool UnitTestSplit::run() {
bool bTestSuccess = true;
struct LTest {
LTest(
const std::string &sStr,
const std::string &sDelim,
const std::vector<std::string> &vExpectedVector
) {
this->sStr = sStr;
this->sDelim = sDelim;
this->vExpectedVector = vExpectedVector;
};
std::string sStr;
std::string sDelim;
std::vector<std::string> vExpectedVector;
};
std::vector<LTest> tests;
tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));
for (int i = 0; i < tests.size(); i++) {
LTest test = tests[i];
std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
for (int n = 0; n < nMin; n++) {
compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
}
}
return bTestSuccess;
}
i use pointer arithmetic. inner while for string delimeter if you satify with char delim just remove inner while simply. i hope it is correct. if you notice any mistake or improve please leave the comment.
std::vector<std::string> split(std::string s, std::string delim)
{
char *p = &s[0];
char *d = &delim[0];
std::vector<std::string> res = {""};
do
{
bool is_delim = true;
char *pp = p;
char *dd = d;
while (*dd && is_delim == true)
if (*pp++ != *dd++)
is_delim = false;
if (is_delim)
{
p = pp - 1;
res.push_back("");
}
else
*(res.rbegin()) += *p;
} while (*p++);
return res;
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.push_back(std::forward<T>(t)), void()) {
c.push_back(std::forward<T>(t));
}
template<typename C, typename T>
auto insert_in_container(C& c, T&& t) -> decltype(c.insert(std::forward<T>(t)), void()) {
c.insert(std::forward<T>(t));
}
template<typename Container>
Container splitR(const std::string& input, const std::string& delims) {
Container out;
size_t delims_len = delims.size();
auto begIdx = 0u;
auto endIdx = input.find(delims, begIdx);
if (endIdx == std::string::npos && input.size() != 0u) {
insert_in_container(out, input);
}
else {
size_t w = 0;
while (endIdx != std::string::npos) {
w = endIdx - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
begIdx = endIdx + delims_len;
endIdx = input.find(delims, begIdx);
}
w = input.length() - begIdx;
if (w != 0) insert_in_container(out, input.substr(begIdx, w));
}
return out;
}
A simpler solution would be -
You can use strtok to delimit on the basis of multichar delimiter.
Remember to use strdup so that the orignal string isn't mutated.
#include <stdio.h>
#include <string.h>
const char* str = "scott>=tiger";
char *token = strtok(strdup(str), ">=");
while (token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, ">=");
}
I looked through the answers and haven't seen an iterator based approach that can be fed into a range loop, so I made one.
This uses C++17 string_views so it shouldn't allocate copies of the string.
struct StringSplit
{
struct Iterator
{
size_t tokenStart_ = 0;
size_t tokenEnd_ = 0;
std::string str_;
std::string_view view_;
std::string delimiter_;
bool done_ = false;
Iterator()
{
// End iterator.
done_ = true;
}
Iterator(std::string str, std::string delimiter)
: str_{std::move(str)}, view_{str_}, delimiter_{
std::move(delimiter)}
{
tokenEnd_ = view_.find(delimiter_, tokenStart_);
}
std::string_view operator*()
{
return view_.substr(tokenStart_, tokenEnd_ - tokenStart_);
}
Iterator &operator++()
{
if (tokenEnd_ == std::string::npos)
{
done_ = true;
return *this;
}
tokenStart_ = tokenEnd_ + delimiter_.size();
tokenEnd_ = view_.find(delimiter_, tokenStart_);
return *this;
}
bool operator!=(Iterator &other)
{
// We only check if both points to the end.
if (done_ && other.done_)
{
return false;
}
return true;
}
};
Iterator beginIter_;
StringSplit(std::string str, std::string delim)
: beginIter_{std::move(str), std::move(delim)}
{
}
Iterator begin()
{
return beginIter_;
}
Iterator end()
{
return Iterator{};
}
};
And example usage would be:
int main()
{
for (auto token : StringSplit{"<>foo<>bar<><>bar<><>baz<><>", "<>"})
{
std::cout << "TOKEN: '" << token << "'" << std::endl;
}
}
Which prints:
TOKEN: ''
TOKEN: 'foo'
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'bar'
TOKEN: ''
TOKEN: 'baz'
TOKEN: ''
TOKEN: ''
It properly handles empty entries at the beginning and end of the string.
Here is an example of splitting a string with another string using Boost String Algorithms library and Boost Range library. The solution is inspired with (modest) suggestion from the the StringAlgo library documentation, see the Split section.
Below is a complete program with the split_with_string function as well as comprehensive test - try it with godbolt:
#include <iostream>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
std::vector<std::string> split_with_string(std::string_view s, std::string_view search)
{
if (search.empty()) return {std::string{s}};
std::vector<boost::iterator_range<std::string_view::iterator>> found;
boost::algorithm::ifind_all(found, s, search);
if (found.empty()) return {};
std::vector<std::string> parts;
parts.reserve(found.size() + 2); // a bit more
std::string_view::iterator part_begin = s.cbegin(), part_end;
for (auto& split_found : found)
{
// do not skip empty extracts
part_end = split_found.begin();
parts.emplace_back(part_begin, part_end);
part_begin = split_found.end();
}
if (part_end != s.end())
parts.emplace_back(part_begin, s.end());
return parts;
}
#define TEST(expr) std::cout << ((!(expr)) ? "FAIL" : "PASS") << ": " #expr "\t" << std::endl
int main()
{
auto s0 = split_with_string("adsf-+qwret-+nvfkbdsj", "");
TEST(s0.size() == 1);
TEST(s0.front() == "adsf-+qwret-+nvfkbdsj");
auto s1 = split_with_string("adsf-+qwret-+nvfkbdsj", "-+");
TEST(s1.size() == 3);
TEST(s1.front() == "adsf");
TEST(s1.back() == "nvfkbdsj");
auto s2 = split_with_string("-+adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s2.size() == 5);
TEST(s2.front() == "");
TEST(s2.back() == "");
auto s3 = split_with_string("-+adsf-+qwret-+nvfkbdsj", "-+");
TEST(s3.size() == 4);
TEST(s3.front() == "");
TEST(s3.back() == "nvfkbdsj");
auto s4 = split_with_string("adsf-+qwret-+nvfkbdsj-+", "-+");
TEST(s4.size() == 4);
TEST(s4.front() == "adsf");
TEST(s4.back() == "");
auto s5 = split_with_string("dbo.abc", "dbo.");
TEST(s5.size() == 2);
TEST(s5.front() == "");
TEST(s5.back() == "abc");
auto s6 = split_with_string("dbo.abc", ".");
TEST(s6.size() == 2);
TEST(s6.front() == "dbo");
TEST(s6.back() == "abc");
}
Tests output:
PASS: s0.size() == 1
PASS: s0.front() == "adsf-+qwret-+nvfkbdsj"
PASS: s1.size() == 3
PASS: s1.front() == "adsf"
PASS: s1.back() == "nvfkbdsj"
PASS: s2.size() == 5
PASS: s2.front() == ""
PASS: s2.back() == ""
PASS: s3.size() == 4
PASS: s3.front() == ""
PASS: s3.back() == "nvfkbdsj"
PASS: s4.size() == 4
PASS: s4.front() == "adsf"
PASS: s4.back() == ""
PASS: s5.size() == 2
PASS: s5.front() == ""
PASS: s5.back() == "abc"
PASS: s6.size() == 2
PASS: s6.front() == "dbo"
PASS: s6.back() == "abc"