Replacing chars with padded index

Viewed 84

I Have a string with special chars and i have to replace those chars with an index (padded n '0' left).

Fast example for better explanation:

I have the string "0980 0099 8383 $$$$" and an index (integer) 3 result should be "0980 0099 8383 0003"

The special characters are not necessarily in sequence. the source string could be empty or it may not contain any special characters

I've already written functions that works.

public static class StringExtensions
{
    public static string ReplaceCounter(this string source, int counter, string character)
    {
        string res = source;

        try
        {
            if (!string.IsNullOrEmpty(character))
            {
                if (res.Contains(character))
                {
                    // Get ALL Indexes position of character
                    var Indexes = GetIndexes(res, character);

                    int max = GetMaxValue(Indexes.Count);

                    while (counter >= max) 
                    { 
                        counter -= max; 
                    }

                    var new_value = counter.ToString().PadLeft(Indexes.Count, '0');

                    for (int i = 0; i < Indexes.Count; i++)
                    {
                        res = res.Remove(Indexes[i], 1).Insert(Indexes[i], new_value[i].ToString());
                    }
                }
            }
        }
        catch (Exception)
        {
            res = source;
        }

        return res;
    }

    private static List<int> GetIndexes(string mainString, string toFind)
    {
        var Indexes = new List<int>();

        for (int i = mainString.IndexOf(toFind); i > -1; i = mainString.IndexOf(toFind, i + 1))
        {
            // for loop end when i=-1 (line.counter not found)
            Indexes.Add(i);
        }

        return Indexes;
    }

    private static int GetMaxValue(int numIndexes)
    {
        int max = 0;

        for (int i = 0; i < numIndexes; i++)
        {
            if (i == 0)
                max = 9;
            else
                max = max * 10 + 9;
        }

        return max;
    }
}

but i don't really like it (first of all because i'm passing the char as string.. and not as a char).

string source = "000081059671####=1811";
int index = 5;
string character = "#";

string result = source.ReplaceCounter(index, character);

can it be more optimized and compact? Can some good soul help me?

Thanks in advance

EDIT

The index is variable so:

If the index is 15

string source = "000081059671####=1811";
int index = 15;
string character = "#";

string result = source.ReplaceCounter(index, character);
// result = "0000810596710015=1811"

it should be a check if the index > max number in my code i posted above, if this case happened i remove from index the "max" value until index < max number

What is mux number? if the special chars number is 4 (as in the example below) the max number will be 9999

string source = "000081059671####=1811";
// max number 9999
2 Answers

Yet another edit

From a comment it seems that more than one digit can be used. In this case the counter can be converted to a string and treated as a char[] to pick the character to use in each iteration :

public static string ReplaceCounter(this string source, 
                                    int counter, 
                                    char character) 
{
    var sb=new StringBuilder(source);
    var replacements=counter.ToString();
    int r=replacements.Length-1;
    for(int i=sb.Length-1;i>=0;i--)
    {
        if(sb[i]==character)
        {
            sb[i]=r>=0 ? replacements[r--] : '0';
            
        }
    }
    return sb.ToString();
}

This can be used for any number of digits."0980 0099 8383 $$$$".ReplaceCounter(15,'$') produces 0980 0099 8383 0015

An edit

After posting the original answer I remembered one can modify a string without allocations by using a StringBuilder. In this case, the last match needs to be replaced with one character, all other matches with another. This ca be a simple reverse iteration :

public static string ReplaceCounter(this string source, 
                                    int counter, 
                                    char character) 
{
    var sb=new StringBuilder(source);
    bool useChar=true;
    for(int i=sb.Length-1;i>=0;i--)
    {
        if(sb[i]==character)
        {
            sb[i]=useChar?(char)('0'+counter):'0';
            useChar=false;
        }
    }
    return sb.ToString();
}

Console.WriteLine("0000##81#059671####=1811".ReplaceCounter(5,'#'));
Console.WriteLine("0980 0099 8383 $$$$".ReplaceCounter(3,'$'));
------
0000008100596710005=1811
0980 0099 8383 0003

Original Answer

Any string modification operation produces a new temporary string that need to be garbage collected. This adds up so quickly that avoiding temporary strings can result in >10x speed improvements when processing lots of text or lots of requests. That's better than using parallel processing.

You can use Regex.Replace to perform complex replacements without allocating temporary strings. You can use one of the Replace overloads that use a MatchEvaluator to produce dynamic output, not just a single value.

In this case :

var source = "0000##81#059671####=1811";

var result = Regex.Replace(source,"#", m=>m.NextMatch().Success?"0":"5");

Console.WriteLine(result);
--------
0000008100596710005=1811

Match.NextMatch() returns the next match in the source, so m.NextMatch().Success can be used to identify the last match and replace it with the index.

This would fail if the character was one of the Regex pattern characters. This can be avoided by escaping the character with Regex.Escape(string)

This can be packed in an extension method

public static string ReplaceCounter(this string source, 
                                    int counter,
                                    string character) 
{
    return Regex.Replace(source,
               Regex.Escape(character), 
               m=>m.NextMatch().Success?"0":counter.ToString());
}

public static string ReplaceCounter(this string source, 
                                    int counter, 
                                    char character) 
    =>ReplaceCounter(source,counter,character.ToString());

This code

var source= "0980 0099 8383 $$$$";
var result=source.ReplaceCounter(5,"$");

Returns

0980 0099 8383 0003

I would suggest such solutiuon (got rid out of helper methods:

public static class StringExtensions
{
    public static string ReplaceCounter(this string source, int counter, char character)
    {
        string res = source;
        string strCounter = counter.ToString();
        bool counterTooLong = false;
        int idx;
        // Going from the and backwards, we fill with counter digits.
        for(int i = strCounter.Length - 1; i >= 0; i--)
        {
            idx = res.LastIndexOf(character);
            // if we run out of special characters, break the loop.
            if (idx == -1)
            {
                counterTooLong = true;
                break;
            }

            res = res.Remove(idx, 1).Insert(idx, strCounter[i].ToString());
        }
        // If we could not fit the counter, we simply throw exception
        if (counterTooLong) throw new InvalidOperationException();
        // If we did not fill all placeholders, we fill it with zeros.
        while (-1 != (idx = res.IndexOf(character))) res = res.Remove(idx, 1).Insert(idx, "0");

        return res;
    }
}

Here's fiddle

Related