Shadow2531
[H]ard|Gawd
- Joined
- Jun 13, 2003
- Messages
- 1,670
I often need a find and replace function that finds all instances of a string in a string and replaces each instance with some other string.
I can use regex_replace() from the boost library ( http://www.boost.org/ )
e.g.
string x = "1a2a3a4a5";
string n = regex_replace( x, regex("a"), "b");
However, the find string and the relpacement string are not treated as literals so you have to escape lots of things.
You can make the replacement a literal by doing:
regex_replace( x, regex("a"), "b", format_literal);
However, sometimes I get crashes with the format_literal flag.
Also, in situations where the regex() could be anything, certain strings cause crashes too, because it's an expression and not a literal.
regex_replace is a pain in many cases and is overkill, when all I want to do is find and replace all. Plus, I don't like having the boost library as a dependency for something like this, but regex_replace performs replacement pretty fast, which is a pro.
Anyway, I decided to make my own find and replace function using an ostringsream object for building the new string. I used a copy of the source string that I could modify. Each time through the loop, I'd find the first instance, get the substring before it and write that substring and the replacement to the ostringstream object. I'd then resize the copy of the source string to a substr() of itself; representing everything after the instance found.
Basically, that worked fine and was even fast, but then, I decided to test it on a 1MB text file where every char was the same. I then tried to replace each character with another and then write it to a file. That's where I hit the speed problem.
The replacement on 1M characters was taking about 1 hour 30 minutes or more to complete, which is just insane. I then changed the function to just iterate if the find string was just one character. I thought that fixed the speed problem, but if I searched every 2 or more characters and replaced them, it was still insanely slow.
Here's the function I was using:
As you can see in last loop, I was trying to make resizing the copy of the source string faster and was trying to avoid using substr() or methods like erase(). However, none of that helped.
I figured the best way to speed things up was to not modify a string at all and to just read a range of characters from the source string right into the stringstream object.
I decided to use write() for that part, but I was still having problems trying to keep track of and find the position of each instance using find(). Well, I decided to use the find() that allows you to specifiy a start position.
Here's the new version and a few tests. This version is fast. It's faster than regex_replace (in my testing). In fact, these same tests with the above code took over 3 hours to complete, but with this version, it's virtually instant.
Now that the function is fast and passes the few tests I've given it, do you see any problems with it? ( like replacements that it fails to do right or segfaults, memory leaking etc). If so, can you help fix them.
How can I make that even better? I definitely want it to be 100% accurate, 100% of the time.
I didn't test with files larger than 1MB yet.
Thanks
I can use regex_replace() from the boost library ( http://www.boost.org/ )
e.g.
string x = "1a2a3a4a5";
string n = regex_replace( x, regex("a"), "b");
However, the find string and the relpacement string are not treated as literals so you have to escape lots of things.
You can make the replacement a literal by doing:
regex_replace( x, regex("a"), "b", format_literal);
However, sometimes I get crashes with the format_literal flag.
Also, in situations where the regex() could be anything, certain strings cause crashes too, because it's an expression and not a literal.
regex_replace is a pain in many cases and is overkill, when all I want to do is find and replace all. Plus, I don't like having the boost library as a dependency for something like this, but regex_replace performs replacement pretty fast, which is a pro.
Anyway, I decided to make my own find and replace function using an ostringsream object for building the new string. I used a copy of the source string that I could modify. Each time through the loop, I'd find the first instance, get the substring before it and write that substring and the replacement to the ostringstream object. I'd then resize the copy of the source string to a substr() of itself; representing everything after the instance found.
Basically, that worked fine and was even fast, but then, I decided to test it on a 1MB text file where every char was the same. I then tried to replace each character with another and then write it to a file. That's where I hit the speed problem.
The replacement on 1M characters was taking about 1 hour 30 minutes or more to complete, which is just insane. I then changed the function to just iterate if the find string was just one character. I thought that fixed the speed problem, but if I searched every 2 or more characters and replaced them, it was still insanely slow.
Here's the function I was using:
Code:
string replaceAll( const string& content, const string& instance, const string& replacement ) {
if ( instance.empty() || content.empty() || content.find(instance) == string::npos || instance == replacement ) {
return content;
}
ostringstream new_content;
if (instance.size() == 1) {
for (string::const_iterator i = content.begin(); i != content.end(); ++i) {
if ( *i == instance[0] ) {
new_content << replacement;
} else {
new_content << *i;
}
}
} else {
string remaining( content );
const size_t instance_size( instance.size() );
for ( size_t start_pos_of_instance; ( start_pos_of_instance = remaining.find(instance) ) != string::npos; ) {
new_content.write( &remaining[0], start_pos_of_instance); // was using remaining.substr()
new_content << replacement;
//remaining.erase(0, start_pos_of_instance + instance_size);
ostringstream temp;
temp.write(&remaining[ start_pos_of_instance + instance_size], remaining.size() - (start_pos_of_instance + instance_size) );
remaining = temp.str();
temp.clear();
}
new_content << remaining;
}
return new_content.str();
}
As you can see in last loop, I was trying to make resizing the copy of the source string faster and was trying to avoid using substr() or methods like erase(). However, none of that helped.
I figured the best way to speed things up was to not modify a string at all and to just read a range of characters from the source string right into the stringstream object.
I decided to use write() for that part, but I was still having problems trying to keep track of and find the position of each instance using find(). Well, I decided to use the find() that allows you to specifiy a start position.
Here's the new version and a few tests. This version is fast. It's faster than regex_replace (in my testing). In fact, these same tests with the above code took over 3 hours to complete, but with this version, it's virtually instant.
Code:
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
using namespace std;
inline string replaceAll( const string& s, const string& f, const string& r ) {
if ( s.empty() || f.empty() || f == r || s.find(f) == string::npos ) {
return s;
}
ostringstream build_it;
size_t i = 0;
for ( size_t pos; ( pos = s.find( f, i ) ) != string::npos; ) {
build_it.write( &s[i], pos - i );
build_it << r;
i = pos + f.size();
}
if ( i != s.size() ) {
build_it.write( &s[i], s.size() - i );
}
return build_it.str();
}
int main() {
string example;
for (size_t i = 0; i < 1048576; ++i) {
example += "a";
}
cout << replaceAll( "one two one two one cbhg", "one ", "d") << endl; // example
cout << replaceAll( "aaaaaaaaaa", "aa","44") << endl; // example
cout << replaceAll( "aaaaaaaaaa", "a","4") << endl; // example
cout << replaceAll( "1a2a3a4a5", "a", "") << endl;
const string s( replaceAll( example, "aa", "44") );
ofstream out("checkoutput.txt");
if (!out) {
return 1;
}
out << s; // file should consist of 1048576 4s
const string s2( replaceAll( example, "a", "4") );
ofstream out2("checkoutput2.txt");
if (!out2) {
return 1;
}
out2 << s2; // file should consist of 1048576 4s
}
Now that the function is fast and passes the few tests I've given it, do you see any problems with it? ( like replacements that it fails to do right or segfaults, memory leaking etc). If so, can you help fix them.
How can I make that even better? I definitely want it to be 100% accurate, 100% of the time.
I didn't test with files larger than 1MB yet.
Thanks