web-dev-qa-db-fra.com

Remplacer plusieurs caractères dans une chaîne, le moyen le plus rapide?

J'importe un certain nombre d'enregistrements avec plusieurs champs string d'une ancienne base de données vers une nouvelle base de données. Cela semble être très lent et je suppose que c'est parce que je fais ceci:

foreach (var oldObj in oldDB)
{
    NewObject newObj = new NewObject();
    newObj.Name = oldObj.Name.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Surname = oldObj.Surname.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Address = oldObj.Address.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Note = oldObj.Note.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    /*
    ... some processing ...
    */
}

Maintenant, j'ai lu des articles et des articles sur le Net où j'ai eu différentes idées à ce sujet. Certains disent que c'est mieux si je fais une regex avec MatchEvaluator, d'autres disent que c'est le meilleur moyen de le laisser tel quel.

Bien qu'il soit possible qu'il soit plus facile pour moi de faire un cas de référence pour moi-même, j'ai décidé de poser une question ici au cas où quelqu'un s'interrogerait sur la même question, ou si quelqu'un le savait à l'avance.

Alors, quel est le moyen le plus rapide de faire cela en C #?

MODIFIER

J'ai posté la référence ici . À première vue, il semble que le chemin de Richard soit le plus rapide. Cependant, sa manière, ni celle de Marc, ne ferait rien à cause du mauvais motif de Regex. Après avoir corrigé le motif de

@"\^@\[\]`\}~\{\\" 

à 

@"\^|@|\[|\]|`|\}|~|\{|\\" 

il semble que l'ancienne méthode avec les appels chaînés .Replace () soit la plus rapide après tout

24
Dejan Janjušević

Merci pour vos contributions les gars .J'ai écrit un test rapide et sale pour tester vos entrées. J'ai testé l'analyse de 4 chaînes avec 500 000 itérations et 4 passes. Le résultat est le suivant:

 *** Pass 1 
 Old (Chaîne String.Replace ()) ainsi terminé en 814 ms 
 Logicnp (ToCharArray) terminé en 916 ms 
 Oleksii (StringBuilder) terminé en 943 ms
 André Christoffer Andersen (méthode Lambda avec agrégat) terminé en 2551 ms 
 Richard (méthode Regex avec MatchEvaluator) complété en 215 ms 
 Marc Gravell (Méthode regex statique) complété en 1008 ms 
.____ . *** Passe 2 
 Ancien (Chained String.Replace ()) terminé en 786 ms 
 Logicnp (ToCharArray) terminé en 920 ms 
 Oleksii (StringBuilder) terminé en 905 ms 
 André Christoffer Andersen (Lambda w/Aggregate) complété en 2515 ms 
 Richard (Regex w/MatchEvaluator) moyen complété en 217 ms. Marc Gravell (Static Regex) complété en 1025 ms 

 * ** Passe 3 
 Ancien (Chained String.Replace ()) manière complétée en 775 ms 
 Logicnp (ToCharArray) manière complétée en 903 ms 
 Oleksii (StringBuilder) complétée en 931 ms 
 André Christoffer Andersen (Lambda w/Aggregate) manière achevée en 2529 ms
Rich ard (Regex w/MatchEvaluator) manière complétée en 214 ms 
 Marc Gravell (Static Regex) manière complétée en 1022 ms 

 *** Réussir 4 
 Vieux (Chaîne String.Replace ()) complété en 799 ms 
 logicnp (ToCharArray) moyen terminé en 908 ms 
 oleksii (StringBuilder) moyen complété en 938 ms. André Christoffer Andersen (Lambda w/Aggregate) moyen terminé en 2592 ms____ Richard ( Regex avec MatchEvaluator) terminée en 225 ms 
 Marc Gravell (Statique Regex) terminée en 1050 ms 

Le code de cette référence est ci-dessous. Veuillez revoir le code et confirmer que @Richard a le moyen le plus rapide. Notez que je n'ai pas vérifié si les sorties étaient correctes, j'ai supposé qu'elles l'étaient.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace StringReplaceTest
{
    class Program
    {
        static string test1 = "A^@[BCD";
        static string test2 = "E]FGH\\";
        static string test3 = "ijk`l}m";
        static string test4 = "nopq~{r";

        static readonly Dictionary<char, string> repl =
            new Dictionary<char, string> 
            { 
                {'^', "Č"}, {'@', "Ž"}, {'[', "Š"}, {']', "Ć"}, {'`', "ž"}, {'}', "ć"}, {'~', "č"}, {'{', "š"}, {'\\', "Đ"} 
            };

        static readonly Regex replaceRegex;

        static Program() // static initializer 
        {
            StringBuilder pattern = new StringBuilder().Append('[');
            foreach (var key in repl.Keys)
                pattern.Append(Regex.Escape(key.ToString()));
            pattern.Append(']');
            replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
        }

        public static string Sanitize(string input)
        {
            return replaceRegex.Replace(input, match =>
            {
                return repl[match.Value[0]];
            });
        } 

        static string DoGeneralReplace(string input) 
        { 
            var sb = new StringBuilder(input);
            return sb.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ').ToString(); 
        }

        //Method for replacing chars with a mapping 
        static string Replace(string input, IDictionary<char, char> replacementMap)
        {
            return replacementMap.Keys
                .Aggregate(input, (current, oldChar)
                    => current.Replace(oldChar, replacementMap[oldChar]));
        } 

        static void Main(string[] args)
        {
            for (int i = 1; i < 5; i++)
                DoIt(i);
        }

        static void DoIt(int n)
        {
            Stopwatch sw = new Stopwatch();
            int idx = 0;

            Console.WriteLine("*** Pass " + n.ToString());
            // old way
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = test1.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result2 = test2.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result3 = test3.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result4 = test4.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
            }
            sw.Stop();
            Console.WriteLine("Old (Chained String.Replace()) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            Dictionary<char, char> replacements = new Dictionary<char, char>();
            replacements.Add('^', 'Č');
            replacements.Add('@', 'Ž');
            replacements.Add('[', 'Š');
            replacements.Add(']', 'Ć');
            replacements.Add('`', 'ž');
            replacements.Add('}', 'ć');
            replacements.Add('~', 'č');
            replacements.Add('{', 'š');
            replacements.Add('\\', 'Đ');

            // logicnp way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                char[] charArray1 = test1.ToCharArray();
                for (int i = 0; i < charArray1.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test1[i], out newChar))
                        charArray1[i] = newChar;
                }
                string result1 = new string(charArray1);

                char[] charArray2 = test2.ToCharArray();
                for (int i = 0; i < charArray2.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test2[i], out newChar))
                        charArray2[i] = newChar;
                }
                string result2 = new string(charArray2);

                char[] charArray3 = test3.ToCharArray();
                for (int i = 0; i < charArray3.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test3[i], out newChar))
                        charArray3[i] = newChar;
                }
                string result3 = new string(charArray3);

                char[] charArray4 = test4.ToCharArray();
                for (int i = 0; i < charArray4.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test4[i], out newChar))
                        charArray4[i] = newChar;
                }
                string result4 = new string(charArray4);
            }
            sw.Stop();
            Console.WriteLine("logicnp (ToCharArray) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // oleksii way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = DoGeneralReplace(test1);
                string result2 = DoGeneralReplace(test2);
                string result3 = DoGeneralReplace(test3);
                string result4 = DoGeneralReplace(test4);
            }
            sw.Stop();
            Console.WriteLine("oleksii (StringBuilder) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // André Christoffer Andersen way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Replace(test1, replacements);
                string result2 = Replace(test2, replacements);
                string result3 = Replace(test3, replacements);
                string result4 = Replace(test4, replacements);
            }
            sw.Stop();
            Console.WriteLine("André Christoffer Andersen (Lambda w/ Aggregate) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Richard way
            sw.Reset();
            sw.Start();
            Regex reg = new Regex(@"\^|@|\[|\]|`|\}|~|\{|\\");
            MatchEvaluator eval = match =>
            {
                switch (match.Value)
                {
                    case "^": return "Č";
                    case "@": return "Ž";
                    case "[": return "Š";
                    case "]": return "Ć";
                    case "`": return "ž";
                    case "}": return "ć";
                    case "~": return "č";
                    case "{": return "š";
                    case "\\": return "Đ";
                    default: throw new Exception("Unexpected match!");
                }
            };
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = reg.Replace(test1, eval);
                string result2 = reg.Replace(test2, eval);
                string result3 = reg.Replace(test3, eval);
                string result4 = reg.Replace(test4, eval);
            }
            sw.Stop();
            Console.WriteLine("Richard (Regex w/ MatchEvaluator) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Marc Gravell way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Sanitize(test1);
                string result2 = Sanitize(test2);
                string result3 = Sanitize(test3);
                string result4 = Sanitize(test4);
            }
            sw.Stop();
            Console.WriteLine("Marc Gravell (Static Regex) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms\n");
        }
    }
}
26
Dejan Janjušević

le moyen le plus rapide

Le seul moyen est de comparer la performance vous-même. Essayez comme dans le Q en utilisant StringBuilder et aussi Regex.Replace.

Mais les micro-critères ne prennent pas en compte la portée de l'ensemble du système. Si cette méthode ne représente qu'une petite fraction du système global, ses performances n'ont probablement aucune incidence sur les performances de l'application.

Quelques notes:

  1. Utiliser String comme ci-dessus (je suppose) va créer beaucoup de chaînes intermédiaires: plus de travail pour le GC. Mais c'est simple.
  2. Utiliser StringBuilder permet de modifier les mêmes données sous-jacentes à chaque remplacement. Cela crée moins de déchets. C'est presque aussi simple que d'utiliser String.
  3. L'utilisation de regex est très complexe (car vous devez disposer d'un code pour résoudre le remplacement), mais permet une seule expression. Je m'attendrais à ce que cela soit plus lent à moins que la liste des remplacements ne soit très longue et que les remplacements soient rares dans la chaîne d'entrée (c'est-à-dire que la plupart des appels de méthode de remplacement ne remplacent rien, coûtant simplement une recherche dans la chaîne).

Je m'attends à ce que le n ° 2 soit légèrement plus rapide qu'une utilisation répétée (des milliers de fois) en raison d'une charge de charge moindre du CPG.

Pour l’approche regex, vous avez besoin de quelque chose comme:

newObj.Name = Regex.Replace(oldObj.Name.Trim(), @"[@^\[\]`}~{\\]", match => {
  switch (match.Value) {
    case "^": return "Č";
    case "@": return "Ž";
    case "[": return "Š";
    case "]": return "Ć";
    case "`": return "ž";
    case "}": return "ć";
    case "~": return "č";
    case "{": return "š";
    case "\\": return "Đ";
    default: throw new Exception("Unexpected match!");
  }
});

Cela pourrait être fait de manière réutilisable en paramétrant avec un Dictionary<char,char> pour contenir les remplacements et réutilisable MatchEvaluator .

15
Richard

Essaye ça:

Dictionary<char, char> replacements = new Dictionary<char, char>();
// populate replacements

string str = "mystring";
char []charArray = str.ToCharArray();

for (int i = 0; i < charArray.Length; i++)
{
    char newChar;
    if (replacements.TryGetValue(str[i], out newChar))
    charArray[i] = newChar;
}

string newStr = new string(charArray);
9
logicnp

Une solution possible consiste à utiliser une classe StringBuilder pour cela.

Vous pouvez d'abord refactoriser le code en une seule méthode

public string DoGeneralReplace(string input)
{
    var sb = new StringBuilder(input);
    sb.Replace("^", "Č")
      .Replace("@", "Ž") ...;
}


//usage
foreach (var oldObj in oldDB)
{
    NewObject newObj = new NewObject();
    newObj.Name = DoGeneralReplace(oldObj.Name);
    ...
}
6
oleksii

Vous pouvez utiliser des expressions lambda pour cela en utilisant Aggregate sur une char map

  //Method for replacing chars with a mapping
  static string Replace(string input, IDictionary<char, char> replacementMap) {
      return replacementMap.Keys
          .Aggregate(input, (current, oldChar) 
              => current.Replace(oldChar, replacementMap[oldChar]));
  }

Vous pouvez exécuter ceci comme suit:

  private static void Main(string[] args) {
      //Char to char map using <oldChar, newChar>
      var charMap = new Dictionary<char, char>();
      charMap.Add('-', 'D'); charMap.Add('|', 'P'); charMap.Add('@', 'A');

      //Your input string
      string myString = "asgjk--@dfsg||jshd--f@jgsld-kj|rhgunfh-@-nsdflngs";

      //Your own replacement method
      myString = Replace(myString, charMap);

      //out: myString = "asgjkDDAdfsgPPjshdDDfAjgsldDkjPrhgunfhDADnsdflngs"
  }
3
André C. Andersen

Eh bien, je essayer faire quelque chose comme:

    static readonly Dictionary<char, string> replacements =
       new Dictionary<char, string>
    {
        {']',"Ć"}, {'~', "č"} // etc
    };
    static readonly Regex replaceRegex;
    static YourUtilityType() // static initializer
    {
        StringBuilder pattern = new StringBuilder().Append('[');
        foreach(var key in replacements.Keys)
            pattern.Append(Regex.Escape(key.ToString()));
        pattern.Append(']');
        replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
    }
    public static string Sanitize(string input)
    {
        return replaceRegex.Replace(input, match =>
        {
            return replacements[match.Value[0]];
        });
    }

Cela a un seul endroit à maintenir (en haut), et construit une Regex précompilée pour gérer les remplacements. Tous les frais généraux sont effectués en un seul (d'où static).

2
Marc Gravell

Approche hybride StringBuilder utilisant IndexOfAny:

protected String ReplaceChars(String sIn)
{
    int replChar = sIn.IndexOfAny(badChars);
    if (replChar < 0)
        return sIn;

    // Don't even bother making a copy unless you know you have something to swap
    StringBuilder sb = new StringBuilder(sIn, 0, replChar, sIn.Length + 10);
    while (replChar >= 0 && replChar < sIn.Length)
    {
        char? c = sIn[replChar];
        string s = null;
        // This approach lets you swap a char for a string or to remove some
        // If you had a straight char for char swap, you could just have your repl chars in an array with the same ordinals and do it all in 2 lines matching the ordinals.
        switch (c)
        {
            case "^": c = "Č";
            ...
            case '\ufeff': c = null; break;
        }
        if (s != null) sb.Append(s);
        else if (c != null) sb.Append(c);

        replChar++; // skip over what we just replaced
        if (replChar < sIn.Length)
        {
            int nextRepChar = sIn.IndexOfAny(badChars, replChar);
            sb.Append(sIn, replChar, (nextRepChar > 0 ? nextRepChar : sIn.Length) - replChar);
            replChar = nextRepChar;
        }
    }
    return sb.ToString();
}
1
user1664043