高效替换字符串中的字符从一个数组替换另一个数组

时间:2016-04-07 21:54:45

标签: c# algorithm unicode

我遇到的具体问题是我必须用等效的Unicode下标替换化学式中的数字,因此H2SO4 => H 2 SO 4。 (这些下标不是字体调整,它们是特殊的unicode字符。)

所以我最初的剪辑是:

return unit.Replace("2", "₂").
            Replace("3", "₃").
            Replace("4", "₄").
            Replace("5", "₅").
            Replace("6", "₆").
            Replace("7", "₇");

哪个有效,但显然不是特别有效。有关更优化算法的任何建议吗?

5 个答案:

答案 0 :(得分:3)

只有10个可能的下标字符需要替换,大多数化学公式不会太长。出于这个原因,我认为您的实现效率并不是非常低效,我建议您在尝试优化代码之前对其进行基准测试。

但这是我尝试创建一个满足您需求的方法:

    public string ToSubscriptFormula(string input)
    {
        var characters = input.ToCharArray();
        for (var i = 0; i < characters.Length; i++)
        {
            switch (characters[i])
            {
                case '2':
                    characters[i] = '₂';
                    break;
                case '3':
                    characters[i] = '₃';
                    break;
               // case statements omitted
            }
        }
        return new string(characters);
    }

我建议避免使用StringBuilder,除非你附加了大量的字符串,因为创建实例的开销实际上会降低你的代码效率。有关何时应该使用它的详细说明,请参见this post by Jon Skeet

另外,鉴于案例陈述数量有限,我个人认为使用Dictionary<char,char>不会增加任何可读性或性能优势,但在不同的情况下考虑使用它可能会有用。

但是如果你真的不得不对你的方法进行超级优化,你可以用以下代码替换case语句(感谢andrew建议):

public string ToSubscriptFormula(string input)
{
    var characters = input.ToCharArray();
    const int distance = '₀' - '0';    // distance of subscript from digit
    for (var i = 0; i < characters.Length; i++)
    {
        if(char.IsDigit(characters[i]))
        {
            characters[i] = (char) (characters[i] + distance);
        }
    }
    return new string(characters);
}

这里的诀窍是所有下标字符都是连续的,并且int转换为char会给你相应的字符。

最后,正如@nwellnhof在评论中建议的那样,char.IsDigit()对于Unicode Nd Category中的一些非拉丁数字字符将返回true。 如果您的化学式包含此类字符,则应将该语句替换为c >= '0' && c<='9'。这可能会比char.IsDigit略快,但我不确定它是否会在大多数实际情况中产生影响。

答案 1 :(得分:2)

我很想做这样的事情:

    public string replace(string input)
    {
        StringBuilder sb = new StringBuilder();

        Dictionary<char, char> map = new Dictionary<char, char>();
        map.Add('2', '₂');
        map.Add('3', '₃');
        map.Add('4', '₄');
        map.Add('5', '₅');
        map.Add('6', '₆');
        map.Add('7', '₇');

        char tmp;

        foreach(char c in input)
        {
            if (map.TryGetValue(c, out tmp))
                sb.Append(tmp);
            else
                sb.Append(c);
        }

        return sb.ToString();
    }

为简单起见,Dictionary在方法内部定义,但应在范围内的其他位置定义。

所以,很简单,只迭代input字符串一次。对于每个字符,找到匹配的Dictionary条目(如果存在),并将该条目或原始字符附加到StringBuilder,以避免创建多个字符串对象。

答案 2 :(得分:2)

我的第一个想法是关于平衡前缀数字的公式:

E.g。 2H 2(g)+ O 2(g)→2H 2 O(g)

据推测,你不希望这个取代领先的号码吗?

另外,我不确定上面提到为什么只需要8位数(甚至只有6位数)需要更换 - 不是所有数字都需要(0-9)?当然,你自己没有0和1,但你需要它们,例如10。

无论如何,尽管如上所述(我没有尝试实现,因为它不是问题),避免使用StringBuilder并对char数组进行操作似乎是有道理的,我宁愿避免使用大的switch语句。 / p>

public class Program
{
    public static void Main()
    {
        Console.WriteLine(SubscriptNums("C6H12O6"));
    }

    public static string SubscriptNums(string input)
    {       
        char[] replacementChars = { '₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉' };

        int zeroCharIndex = (int)'0';

        char[] inputCharArray = input.ToCharArray();

        for(int i = 0; i < inputCharArray.Length; i++)
        {
            if (inputCharArray[i] >= '0' && inputCharArray[i] <= '9')
            {
                inputCharArray[i] = replacementChars[(int)inputCharArray[i] - zeroCharIndex];
            }
        }

        return new string(inputCharArray);
    }
}

编辑1 - 删除数字值为“0”的幻数。

编辑2 - 删除了对IsDigit的使用。

答案 3 :(得分:0)

您可以迭代字符串并检查每个字符。如果要替换,请将相应的字符附加到StringBuilder。如果没有,只需添加原始字符。这样,您只需迭代字符串一次,而不是每次替换一次。此外,由于字符串是不可变的,每次调用String.Replace()都会为结果创建一个新的字符串副本,该副本将立即再次进行GC。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < unit.Length; i++) {
    switch(unit[i]) {
        case '2': sb.Append('₂'); break;
        case '3': sb.Append('₃'); break;
        ...
        default: sb.Append(unit[i]); break;
    }
}

output = sb.ToString();

你也可以介绍一些替代字典,比如Abdullah Nehir建议

StringBuilder sb = new StringBuilder();
Dictionary<char, char> replacements = new Dictionary<char, char>();
//put in the pairs
for (int i = 0; i < unit.Length; i++) {
    if (replacements.ContainsKey(unit[i]))
        sb.Append(replacement[unit[i]];
    else
        sb.Append(unit[i]);
}

您可以使用foreach循环

迭代字符串,而不是通过索引访问值
foreach (char c in unit) {
   if (replacements.ContainsKey(c))
       sb.Append(replacements[c]);
   else
       sb.Append(c);
}

答案 4 :(得分:0)

如果您正在寻找一些优雅的代码,而不必为每个字符键入string.Replace,那么这将对您有所帮助:

    public static string Replace(string input)
    {
        char[] inputCharArr = input.ToCharArray();
        StringBuilder sb = new StringBuilder();
        foreach (var c in inputCharArr)
        {
            int intC = (int)c;
            //If the digit was a number ([0-9] are [48-57] in unicode),
            //replace the old char with the new char
            //(8272 when added to the unicode of [0-9] gives the desired result)
            if (intC > 47 && intC < 58)
                sb.Append((char)(intC + 8272));
            else sb.Append(c);
        }
        return sb.ToString();
    }

如果您想知道评论的内容,请参阅编辑历史记录。