Replace multiple characters in a C# string

C#.NetString

C# Problem Overview


Is there a better way to replace strings?

I am surprised that Replace does not take in a character array or string array. I guess that I could write my own extension but I was curious if there is a better built in way to do the following? Notice the last Replace is a string not a character.

myString.Replace(';', '\n').Replace(',', '\n').Replace('\r', '\n').Replace('\t', '\n').Replace(' ', '\n').Replace("\n\n", "\n");

C# Solutions


Solution 1 - C#

You can use a replace regular expression.

s/[;,\t\r ]|[\n]{2}/\n/g
  • s/ at the beginning means a search
  • The characters between [ and ] are the characters to search for (in any order)
  • The second / delimits the search-for text and the replace text

In English, this reads:

"Search for ; or , or \t or \r or (space) or exactly two sequential \n and replace it with \n"

In C#, you could do the following: (after importing System.Text.RegularExpressions)

Regex pattern = new Regex("[;,\t\r ]|[\n]{2}");
pattern.Replace(myString, "\n");

Solution 2 - C#

If you are feeling particularly clever and don't want to use Regex:

char[] separators = new char[]{' ',';',',','\r','\t','\n'};

string s = "this;is,\ra\t\n\n\ntest";
string[] temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
s = String.Join("\n", temp);

You could wrap this in an extension method with little effort as well.

Edit: Or just wait 2 minutes and I'll end up writing it anyway :)

public static class ExtensionMethods
{
   public static string Replace(this string s, char[] separators, string newVal)
   {
       string[] temp;
        
       temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
       return String.Join( newVal, temp );
   }
}

And voila...

char[] separators = new char[]{' ',';',',','\r','\t','\n'};
string s = "this;is,\ra\t\n\n\ntest";

s = s.Replace(separators, "\n");

Solution 3 - C#

You could use Linq's Aggregate function:

string s = "the\nquick\tbrown\rdog,jumped;over the lazy fox.";
char[] chars = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
string snew = chars.Aggregate(s, (c1, c2) => c1.Replace(c2, '\n'));

Here's the extension method:

public static string ReplaceAll(this string seed, char[] chars, char replacementCharacter)
{
    return chars.Aggregate(seed, (str, cItem) => str.Replace(cItem, replacementCharacter));
}

Extension method usage example:

string snew = s.ReplaceAll(chars, '\n');

Solution 4 - C#

This is the shortest way:

myString = Regex.Replace(myString, @"[;,\t\r ]|[\n]{2}", "\n");

Solution 5 - C#

Strings are just immutable char arrays

You just need to make it mutable:

  • either by using StringBuilder
  • go in the unsafe world and play with pointers (dangerous though)

and try to iterate through the array of characters the least amount of times. Note the HashSet here, as it avoids to traverse the character sequence inside the loop. Should you need an even faster lookup, you can replace HashSet by an optimized lookup for char (based on an array[256]).

Example with StringBuilder

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace, 
    char replacement)
{
    HashSet<char> set = new HashSet<char>(toReplace);
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set.Contains(currentCharacter))
        {
            builder[i] = replacement;
        }
    }
}

Edit - Optimized version

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace,
    char replacement)
{
    var set = new bool[256];
    foreach (var charToReplace in toReplace)
    {
        set[charToReplace] = true;
    }
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set[currentCharacter])
        {
            builder[i] = replacement;
        }
    }
}

Then you just use it like this:

var builder = new StringBuilder("my bad,url&slugs");
builder.MultiReplace(new []{' ', '&', ','}, '-');
var result = builder.ToString();

Solution 6 - C#

Ohhh, the performance horror! The answer is a bit outdated, but still...

public static class StringUtils
{
    #region Private members

    [ThreadStatic]
    private static StringBuilder m_ReplaceSB;

    private static StringBuilder GetReplaceSB(int capacity)
    {
        var result = m_ReplaceSB;

        if (null == result)
        {
            result = new StringBuilder(capacity);
            m_ReplaceSB = result;
        }
        else
        {
            result.Clear();
            result.EnsureCapacity(capacity);
        }

        return result;
    }


    public static string ReplaceAny(this string s, char replaceWith, params char[] chars)
    {
        if (null == chars)
            return s;

        if (null == s)
            return null;

        StringBuilder sb = null;

        for (int i = 0, count = s.Length; i < count; i++)
        {
            var temp = s[i];
            var replace = false;

            for (int j = 0, cc = chars.Length; j < cc; j++)
                if (temp == chars[j])
                {
                    if (null == sb)
                    {
                        sb = GetReplaceSB(count);
                        if (i > 0)
                            sb.Append(s, 0, i);
                    }

                    replace = true;
                    break;
                }

            if (replace)
                sb.Append(replaceWith);
            else
                if (null != sb)
                    sb.Append(temp);
        }

        return null == sb ? s : sb.ToString();
    }
}

Solution 7 - C#

You may also simply write these string extension methods, and put them somewhere in your solution:

using System.Text;

public static class StringExtensions
{
    public static string ReplaceAll(this string original, string toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(toBeReplaced)) return original;
        if (newValue == null) newValue = string.Empty;
        StringBuilder sb = new StringBuilder();
        foreach (char ch in original)
        {
            if (toBeReplaced.IndexOf(ch) < 0) sb.Append(ch);
            else sb.Append(newValue);
        }
        return sb.ToString();
    }

    public static string ReplaceAll(this string original, string[] toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || toBeReplaced == null || toBeReplaced.Length <= 0) return original;
        if (newValue == null) newValue = string.Empty;
        foreach (string str in toBeReplaced)
            if (!string.IsNullOrEmpty(str))
                original = original.Replace(str, newValue);
        return original;
    }
}


Call them like this:

"ABCDE".ReplaceAll("ACE", "xy");

> xyBxyDxy


And this:

"ABCDEF".ReplaceAll(new string[] { "AB", "DE", "EF" }, "xy");

> xyCxyF

Solution 8 - C#

Use RegEx.Replace, something like this:

  string input = "This is   text with   far  too   much   " + 
                 "whitespace.";
  string pattern = "[;,]";
  string replacement = "\n";
  Regex rgx = new Regex(pattern);
  string result = rgx.Replace(input, replacement);

Here's more info on this MSDN documentation for RegEx.Replace

Solution 9 - C#

Performance-Wise this probably might not be the best solution but it works.

var str = "filename:with&bad$separators.txt";
char[] charArray = new char[] { '#', '%', '&', '{', '}', '\\', '<', '>', '*', '?', '/', ' ', '$', '!', '\'', '"', ':', '@' };
foreach (var singleChar in charArray)
{
   str = str.Replace(singleChar, '_');
}

Solution 10 - C#

string ToBeReplaceCharacters = @"~()@#$%&amp;+,'&quot;&lt;&gt;|;\/*?";
string fileName = "filename;with<bad:separators?";

foreach (var RepChar in ToBeReplaceCharacters)
{
	fileName = fileName.Replace(RepChar.ToString(), "");
}

Solution 11 - C#

A .NET Core version for replacing a defined set of string chars to a specific char. It leverages the recently introduced Span type and string.Create method.

The idea is to prepare a replacement array, so no actual comparison operations would be required for the each string char. Thus, the replacement process reminds the way a state machine works. In order to avoid initialization of all items of the replacement array, let's store oldChar ^ newChar (XOR'ed) values there, what gives the following benefits:

  • If a char is not changing: ch ^ ch = 0 - no need to initialize non-changing items
  • The final char can be found by XOR'ing: ch ^ repl[ch]:
    • ch ^ 0 = ch - not changed chars case
    • ch ^ (ch ^ newChar) = newChar - replaced char

So the only requirement would be to ensure that the replacement array is zero-ed when initialized. We'll be using ArrayPool<char> to avoid allocations each time the ReplaceAll method is called. And, in order to ensure that the arrays are zero-ed without expensive call to Array.Clear method, we'll be maintaining a pool dedicated for the ReplaceAll method. We'll be clearing the replacement array (exact items only) before returning it to the pool.

public static class StringExtensions
{
    private static readonly ArrayPool<char> _replacementPool = ArrayPool<char>.Create();

    public static string ReplaceAll(this string str, char newChar, params char[] oldChars)
    {
        // If nothing to do, return the original string.
        if (string.IsNullOrEmpty(str) ||
            oldChars is null ||
            oldChars.Length == 0)
        {
            return str;
        }

        // If only one character needs to be replaced,
        // use the more efficient `string.Replace`.
        if (oldChars.Length == 1)
        {
            return str.Replace(oldChars[0], newChar);
        }

        // Get a replacement array from the pool.
        var replacements = _replacementPool.Rent(char.MaxValue + 1);

        try
        {
            // Intialize the replacement array in the way that
            // all elements represent `oldChar ^ newChar`.
            foreach (var oldCh in oldChars)
            {
                replacements[oldCh] = (char)(newChar ^ oldCh);
            }

            // Create a string with replaced characters.
            return string.Create(str.Length, (str, replacements), (dst, args) =>
            {
                var repl = args.replacements;

                foreach (var ch in args.str)
                {
                    dst[0] = (char)(repl[ch] ^ ch);
                    dst = dst.Slice(1);
                }
            });
        }
        finally
        {
            // Clear the replacement array.
            foreach (var oldCh in oldChars)
            {
                replacements[oldCh] = char.MinValue;
            }

            // Return the replacement array back to the pool.
            _replacementPool.Return(replacements);
        }
    }
}

Solution 12 - C#

I know this question is super old, but I want to offer 2 options that are more efficient:

1st off, the extension method posted by Paul Walls is good but can be made more efficient by using the StringBuilder class, which is like the string data type but made especially for situations where you will be changing string values more than once. Here is a version I made of the extension method using StringBuilder:

public static string ReplaceChars(this string s, char[] separators, char newVal)
{
    StringBuilder sb = new StringBuilder(s);
    foreach (var c in separators) { sb.Replace(c, newVal); }
    return sb.ToString();
}

I ran this operation 100,000 times and using StringBuilder took 73ms compared to 81ms using string. So the difference is typically negligible, unless you're running many operations or using a huge string.

Secondly, here is a 1 liner loop you can use:

foreach (char c in separators) { s = s.Replace(c, '\n'); }

I personally think this is the best option. It is highly efficient and doesn't require writing an extension method. In my testing this ran the 100k iterations in only 63ms, making it the most efficient. Here is an example in context:

string s = "this;is,\ra\t\n\n\ntest";
char[] separators = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
foreach (char c in separators) { s = s.Replace(c, '\n'); }

Credit to Paul Walls for the first 2 lines in this example.

Solution 13 - C#

I also fiddled around with that problem, and found that most of the solutions here are very slow. The fastest one was actually the LINQ + Aggregate method that dodgy_coder posted.

But I thought, well that might be also quite heavy in memory allocations depending upon how many old characters there are. So I came out with this:

The idea here is to have a cached replacement map of the old characters for the current thread, to safe allocations. And other than that just working with a character array of the input that later on is returned as string again. Whereas the character array is modified as less as possible.

[ThreadStatic]
private static bool[] replaceMap;
public static string Replace(this string input, char[] oldChars, char newChar)
{
    if (input == null) throw new ArgumentNullException(nameof(input));
    if (oldChars == null) throw new ArgumentNullException(nameof(oldChars));
    if (oldChars.Length == 1) return input.Replace(oldChars[0], newChar);
    if (oldChars.Length == 0) return input;

    replaceMap = replaceMap ?? new bool[char.MaxValue + 1];
    foreach (var oldChar in oldChars)
    {
        replaceMap[oldChar] = true;
    }

    try
    {
        var count = input.Length;
        var output = input.ToCharArray();
        for (var i = 0; i < count; i++)
        {
            if (replaceMap[input[i]])
            {
                output[i] = newChar;
            }
        }

        return new string(output);
    }
    finally
    {
        foreach (var oldChar in oldChars)
        {
            replaceMap[oldChar] = false;
        }
    }
}

For me this is at most two allocations for the actual input string to work on. A StringBuilder turned out to be much slower for me for some reasons. And it is 2 times faster than the LINQ variant.

Solution 14 - C#

No "Replace" (Linq only):

	string myString = ";,\r\t \n\n=1;;2,,3\r\r4\t\t5  6\n\n\n\n7=";
	char NoRepeat = '\n';
	string ByeBye = ";,\r\t ";
	string myResult = myString.ToCharArray().Where(t => !"STOP-OUTSIDER".Contains(t))
		         .Select(t => "" + ( ByeBye.Contains(t) ? '\n' : t))
                  .Aggregate((all, next) => (
			          next == "" + NoRepeat && all.Substring(all.Length - 1) == "" + NoRepeat
            	      ? all : all  + next ) );

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionzgirodView Question on Stackoverflow
Solution 1 - C#johnluetkeView Answer on Stackoverflow
Solution 2 - C#Paul WallsView Answer on Stackoverflow
Solution 3 - C#dodgy_coderView Answer on Stackoverflow
Solution 4 - C#ParParView Answer on Stackoverflow
Solution 5 - C#FabView Answer on Stackoverflow
Solution 6 - C#John WhiterView Answer on Stackoverflow
Solution 7 - C#sɐunıɔןɐqɐpView Answer on Stackoverflow
Solution 8 - C#Dmitry SamuylovView Answer on Stackoverflow
Solution 9 - C#Daniel SzékelyView Answer on Stackoverflow
Solution 10 - C#Jignesh BhayaniView Answer on Stackoverflow
Solution 11 - C#AndreyChView Answer on Stackoverflow
Solution 12 - C#SendETHToThisAddressView Answer on Stackoverflow
Solution 13 - C#Christoph MeißnerView Answer on Stackoverflow
Solution 14 - C#Ivan SaginView Answer on Stackoverflow