|
Okay, I see that you try to split and join the full text, but I think that way is some faster...
public string IReplace(string Content, string Pattern, string ReplaceWith)
{
int Pos = 0;
while ((Pos = Content.ToLower().IndexOf(Pattern.ToLower(), Pos)) != -1)
Content = Content.Replace(Content.Substring(Pos++, Pattern.Length), ReplaceWith);
return Content;
}
...not much more faster, but a lil bit faster and much easier to read. 
|
|
|
|
|
My code is the fastest, unless you want to count that useless .Replace function... ^^
I didn't include the VB equivalent, but the comparison speaks for itself anyway.
private readonly static string[] CLEANME = new string[] { "AbC" };
internal static string ReturnCleanedString(string cleanMe, string replacement)
{
StringBuilder sb = new StringBuilder();
sb.Append(cleanMe);
string s = CLEANME[0];
int index = sb.ToString().IndexOf(s, StringComparison.OrdinalIgnoreCase);
int lengthToRemove = s.Length;
while (index != -1)
{
string foundIt = sb.ToString().Substring(index, lengthToRemove);
string temp = sb.ToString().Replace(foundIt, replacement);
sb.Remove(0, sb.Length);
sb.Append(temp);
index = sb.ToString().IndexOf(s, StringComparison.OrdinalIgnoreCase);
}
return sb.ToString();
}
ABCabcAbCaBcAbcabCABCAbcaBC
Myversion = 00:00:00.0012541s
Epner's = 00:00:00.0015566s
ReplaceEx = 00:00:00.0015644s
regexp = 00:00:00.0033268s
abcaBcabCAbc
Myversion = 00:00:00.0008608s
Epner's = 00:00:00.0009639s
ReplaceEx = 00:00:00.0010320s
regexp = 00:00:00.0031109s
abcaBcabC
Myversion = 00:00:00.0006112s
Epner's = 00:00:00.0008772s
ReplaceEx = 00:00:00.0016708s
regexp = 00:00:00.0022278s
abcaBc
Myversion = 00:00:00.0004893s
Epner's = 00:00:00.0007274s
ReplaceEx = 00:00:00.0008103s
regexp = 00:00:00.0011531s
As you can probably tell, I'll be using this to clean strings up by looping through multiple patterns.
This is the code that actually executed during my tests:
string segment = "ABCabcAbCaBcAbcabCABCAbcaBC";
string source;
string pattern = "AbC";
string destination = "";
const long count = 1000;
StringBuilder pressure = new StringBuilder();
Stopwatch time = new Stopwatch();
for (int i = 0; i < count; i++)
{
pressure.Append(segment);
}
source = pressure.ToString();
GC.Collect();
time = new Stopwatch();
time.Start();
string result = Regex.Replace(source, pattern,
destination, RegexOptions.IgnoreCase);
time.Stop();
string RegexExample = "regexp = " + time.Elapsed.ToString() + "s";
GC.Collect();
time = new Stopwatch();
time.Start();
string resultReplaceEx = Global.ReplaceEx(source, pattern, destination);
time.Stop();
string ReplaceExString = "ReplaceEx = " + time.Elapsed.ToString() + "s";
GC.Collect();
time = new Stopwatch();
time.Start();
string resultReplace = source.Replace(pattern.ToLower(), destination);
time.Stop();
string Replace = "Replace = " + time.Elapsed.ToString() + "s";
GC.Collect();
time = new Stopwatch();
time.Start();
string resultMine = Global.ReturnCleanedString(source, "");
time.Stop();
string MyVersion = "My version = " + time.Elapsed.ToString() + "s";
GC.Collect();
time = new Stopwatch();
time.Start();
string resultEpner = Global.ReplaceEpner(source, "Original", "Replacement", StringComparison.OrdinalIgnoreCase);
time.Stop();
string Epner = "Epner's version = " + time.Elapsed.ToString() + "s";
GC.Collect();
StringBuilder myTimeComparison = new StringBuilder();
myTimeComparison.Append(RegexExample + Environment.NewLine +
ReplaceExString + Environment.NewLine +
Replace + Environment.NewLine +
MyVersion + Environment.NewLine +
Epner);
|
|
|
|
|
I think, in real world you want to specify a variable pattern, not a readonly constant, so you have to change your code to:
internal static string ReturnCleanedString(string source, string pattern, string replacement)
{
StringBuilder sb = new StringBuilder();
sb.Append(source);
int index = sb.ToString().IndexOf(pattern, StringComparison.OrdinalIgnoreCase);
int lengthToRemove = pattern.Length;
while (index != -1)
{
string foundIt = sb.ToString().Substring(index, lengthToRemove);
string temp = sb.ToString().Replace(foundIt, replacement);
sb.Remove(0, sb.Length);
sb.Append(temp);
index = sb.ToString().IndexOf(pattern, StringComparison.OrdinalIgnoreCase);
}
return sb.ToString();
}
In this version the ReplaceEx needs 1,3 sek, your code needs 2,6 sek, Eppners code needs 0,98
Maybe you have an improvement?
modified 7-May-13 6:24am.
|
|
|
|
|
It's interesting that you changed my code and then say it's the slowest... You're not only saying it's the slowest, but now everything is taking about a second, instead of thousandths of a second. That means your computer is either slow, or you need to clean it up ALOT... (I have a new computer now that makes the computer I ran the code on originally look slow, so I'm not sure how it would be possible to be even slower 7 months later...) If you had to change it in order to say it's slower, then I'll enjoy that confirmation anyway. =)
At any rate, why don't you try changing it from a readonly string array to just a static string and see how that impacts things? You could always change it at any time, if it wasn't readonly, and that would make it a variable pattern. On the other hand, it may be a function where you always want to remove hyphens, semi-colons, single quotes, ampersands, etc... In that case, a readonly static string array would suit your needs just fine.
Thank you for confirming that my code is the fastest. =) Good luck with your implementation!
modified 8-May-13 8:10am.
|
|
|
|
|
|
Copy & pasted the code, worked like a charm.
|
|
|
|
|
Good explanation of the issue and solution.
|
|
|
|
|
Bad code, causes crash at client side.
|
|
|
|
|
Are you sure it's caused by this code?
Bastard Programmer from Hell
|
|
|
|
|
It is really amazing to see crash by this code
Regards,
unruledboy_at_gmail_dot_com
http://www.xnlab.com
|
|
|
|
|
|
Hi,
I made another function ..thought will share this with you
/// <summary>
/// the following function replaces string strToreplace by string strByReplace in the string strValue. It is same as the .net string.replace but will do the comparision to replace case insensitively
/// </summary>
/// <param name="strValue">the string in which to replace</param>
/// <param name="strToReplace">the string which is to be replaced</param>
/// <param name="strByReplace">the string by which to replace the old value</param>
/// <returns></returns>
public static string ReplaceCaseInsensitive(string strValue, string strToReplace, string strByReplace)
{
int index = strValue.IndexOf(strToReplace, 0, StringComparison.CurrentCultureIgnoreCase);
if (index == -1)
return strValue;
else
{
strToReplace = strValue.Substring(index, strToReplace.Length);
return strValue.Replace(strToReplace, strByReplace);
}
}
}
To use call the function like this
string str = ReplaceCaseInsensitive("Abcd", "BC","++" );
Cheers
Sohail Sayed
|
|
|
|
|
That's a great alternative for some scenarios when the upper-/lower-case structure of the pattern to replace is the same throughout the original string, however it does not work for this:
ReplaceCaseInsensitive("AbcdAbCd", "BC", "++");
But, based on that idea, we could place the main functionality in a loop and do until it's completely done and then check if it's faster than the other alternatives posted here.
|
|
|
|
|
Hi,
If we are talking about pure performance, further optimizations can be done, doing bulk copies of data inside or between arrays, optimized allocation and optimized replacement (i.e. if replacing THESE with THIS, match THESE but only replace ESE with IS). Here is a logic for a simple improvement:
Run source string to find matches and build list of matching positions
IF new string is smaller than the one to be being replaced:
- Do optimized array.copy on char array of the original string and array.copy of new string
ELSE
- Only here alocate the destination char array with the correct size
- Fill the destination char array with arry.copy from original string and array.copy of the replacement string
And if you are really, really into performance, allocate the working/destination array as global variable (as many as you need) only once (yes, they must be big enough to support 99% of your strings - beleave me this pays off if you have to do a lot of repalcements to do).
See you!
|
|
|
|
|
I wrote this a while ago and with such large chunks it seems to be even faster than yours, more flexible and easier to understand ... I win with 2.56s versus 2.99s with your first example and 0.35s versus 0.47s with your last example.
You can use it also with culture-specific comparison, here is the usage for simple case insensitivity:
MyToolsClass.Replace("MyOriginalString", "Original", "Replacement", StringComparison.OrdinalIgnoreCase)
Here goes the method:
static public string Replace(string original, string pattern, string replacement, StringComparison comparisonType)
{
return Replace(original, pattern, replacement, comparisonType, -1);
}
static public string Replace(string original, string pattern, string replacement, StringComparison comparisonType, int stringBuilderInitialSize)
{
if (original == null)
{
return null;
}
if (String.IsNullOrEmpty(pattern))
{
return original;
}
int posCurrent = 0;
int lenPattern = pattern.Length;
int idxNext = original.IndexOf(pattern, comparisonType);
StringBuilder result = new StringBuilder(stringBuilderInitialSize < 0 ? Math.Min(4096, original.Length) : stringBuilderInitialSize);
while (idxNext >= 0)
{
result.Append(original, posCurrent, idxNext - posCurrent);
result.Append(replacement);
posCurrent = idxNext + lenPattern;
idxNext = original.IndexOf(pattern, posCurrent, comparisonType);
}
result.Append(original, posCurrent, original.Length - posCurrent);
return result.ToString();
}
The secret might be the overload of the StringBuilder.Append method which is used here, which allows to append a part of a string without having to create any substring from it. That might be a feature that many have overseen yet.
EDIT: Fixed bug thanks to "Member 551508". Provided overload where you can specify an initial StringBuilder size as inspired by user tmbrye. The default value is the length of the original string, but not larger than 4096. A large initial size will increase performance slightly but also allocate more memory. Also remember when specifying a large size that the result of a large string could theoretically be a very tiny or even empty string).
modified on Thursday, April 9, 2009 5:17 AM
|
|
|
|
|
Indeed you do win- Your method is freakin fast!!!!!!!!!!!!! I just used it to replace the images path on every single page that gets rendered. That's a lot of text. I didn't even notice any difference in speed with using your method or not using it all at. Thanks for sharing!
|
|
|
|
|
Beauty, I need a slight modification however as I want to replace overlapping strings in my csv string. ie ",0," -> ",," or
Replace("blah,0,0,0,00,rawr", ",0,", ",,") -> "blah,,,,00,rawr"
So I'll just change this line
Michael Epner wrote: result.Append(original, idxLast, idxPattern - idxLast);
to
<br />
if(idxLast > idxPattern)<br />
result.Remove(result.Length + idxPattern - idxLast - 1, idxLast - idxPattern);<br />
else<br />
result.Append(original, idxLast, idxPattern - idxLast);<br />
I hope it doesn't affect the performance too much.
|
|
|
|
|
That csv mod is pretty nifty- but I would separate that into a second method that leverages the original ReplaceString method. Something like ReplaceStringCsv which would then allow you to incorporate more csv functions and switches.
By the way, I ended up wrapping your ReplaceString method into a methods extention and is working out excellently in production. This is all I have to do now to use the super fast string replacer:
testStr.ReplaceString("as", "ii", StringComparison.OrdinalIgnoreCase)
...
public static class Extensions
{
static public string ReplaceString(this string original, string pattern,
string replacement, StringComparison comparisonType)
{
if (original == null)
return null;
if (String.IsNullOrEmpty(pattern))
return original;
int lenPattern = pattern.Length;
int idxPattern = -1;
int idxLast = 0;
StringBuilder result = new StringBuilder();
while (true)
{
idxPattern = original.IndexOf(pattern, idxPattern + 1, comparisonType);
if (idxPattern < 0)
{
result.Append(original, idxLast, original.Length - idxLast);
break;
}
result.Append(original, idxLast, idxPattern - idxLast);
result.Append(replacement);
idxLast = idxPattern + lenPattern;
}
return result.ToString();
}
}
|
|
|
|
|
Please see the updated version in my original post, as there was a bug.
|
|
|
|
|
I actually married this code with the original code and found it to be even faster in my testing. Here is the change I made:
Changed:
StringBuilder result = new StringBuilder();
Changed it to:
int inc = (original.Length / pattern.Length) * (replacement.Length - pattern.Length);
StringBuilder result = new StringBuilder(original.Length + Math.Max(0, inc));
|
|
|
|
|
Sure it's a good idea for better performance to initialize the StringBuilder with a starting size. Maybe it could be initialized with the original string size, or there could be an additional parameter for it in the Replace method. Calculating the size like in the example is a bit futile however, because it doesn't take into account that the pattern could appear more than once. Calculating the exact resulting size would make us need to count the occurrences which would take almost the time of building the result string, so that would be some kinda redundant approach too.
|
|
|
|
|
Thanks guys one more wheel I don't need to re-invent 
|
|
|
|
|
It also has a bug. If you do Replace("abababa", "aba", "~", StringComparison.CurrentCulture) you get a Runtime error on this line: result.Append(original, idxLast, original.Length - idxLast);
Here's a tighter VB.NET version of the code with some fixes:
Public Shared Function Replace(ByVal s As String, ByVal oldValue As String, ByVal newValue As String, ByVal comparisonType As StringComparison) As String
If s Is Nothing Then Return Nothing
If String.IsNullOrEmpty(oldValue) OrElse newValue Is Nothing Then Return s
Dim result As New StringBuilder()
Dim lenOldValue As Integer = oldValue.Length
Dim curPosition As Integer = 0
Dim idxNext As Integer = s.IndexOf(oldValue, comparisonType)
While idxNext >= 0
result.Append(s, curPosition, idxNext - curPosition)
result.Append(newValue)
curPosition = idxNext + lenOldValue
idxNext = s.IndexOf(oldValue, curPosition, comparisonType)
End While
result.Append(s, curPosition, s.Length - curPosition)
Return result.ToString()
End Function
And some NUnit tests to prove the fix:
<test()> _
Public Sub TestReplace()
Assert.AreEqual("wxyz wxyz wxyz", StringHelper.Replace("asdf ASDF aSdF", "asdf", "wxyz", StringComparison.CurrentCultureIgnoreCase))
Assert.AreEqual("wxyz ASDF aSdF", StringHelper.Replace("asdf ASDF aSdF", "asdf", "wxyz", StringComparison.CurrentCulture))
Assert.IsNull(StringHelper.Replace(Nothing, "asdf", "wxyz", StringComparison.CurrentCulture))
Assert.AreEqual("", StringHelper.Replace("", "a", "b", StringComparison.CurrentCulture))
Assert.AreEqual("lmnop", StringHelper.Replace("lmnop", Nothing, Nothing, StringComparison.CurrentCulture))
Assert.AreEqual("lmnop", StringHelper.Replace("lmnop", "a", "b", StringComparison.CurrentCulture))
Assert.AreEqual("cbxzxBxzx", StringHelper.Replace("cbABABABA", "aba", "xzx", StringComparison.CurrentCultureIgnoreCase))
Assert.AreEqual("cbABABABA", StringHelper.Replace("cbABABABA", "aba", "xzx", StringComparison.CurrentCulture))
Assert.AreEqual("~b~ba", StringHelper.Replace("ababababa", "aba", "~", StringComparison.CurrentCulture))
Assert.AreEqual("~ABA~b~ABA~ba", StringHelper.Replace("ababababa", "aba", "~ABA~", StringComparison.CurrentCulture))
Assert.AreEqual("~ABA~~ABA~", StringHelper.Replace("abaaba", "aba", "~ABA~", StringComparison.CurrentCulture))
End Sub
<pre>
|
|
|
|
|
Thank you, I fixed the bug and used your modification to update the C# code in my original post.
|
|
|
|
|
Nice work. I've put it into an exention method so it can just be called on a string.
public static string Replace(this string original,
string pattern, string replacement, StringComparison comparisonType)
{
if (original == null)
{
return null;
}
if (String.IsNullOrEmpty(pattern))
{
return original;
}
int lenPattern = pattern.Length;
int idxPattern = -1;
int idxLast = 0;
StringBuilder result = new StringBuilder();
while (true)
{
idxPattern = original.IndexOf(pattern, idxPattern + 1, comparisonType);
if (idxPattern < 0)
{
result.Append(original, idxLast, original.Length - idxLast);
break;
}
result.Append(original, idxLast, idxPattern - idxLast);
result.Append(replacement);
idxLast = idxPattern + lenPattern;
}
return result.ToString();
}
|
|
|
|
|