wake-up-neo.net

Am schnellsten mehrere Zeichen in einer Zeichenfolge ersetzen?

Ich importiere einige Datensätze mit mehreren string Feldern von einer alten Datenbank in eine neue Datenbank. Es scheint sehr langsam zu sein und ich vermute, es liegt daran, dass ich das mache:

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 ...
    */
}

Jetzt habe ich einige Posts und Artikel im Internet gelesen, in denen ich viele verschiedene Gedanken dazu gesehen habe. Einige sagen, es ist besser, wenn ich Regex mit MatchEvaluator mache, andere sagen, es ist das Beste, es so zu lassen, wie es ist.

Es ist zwar möglich, dass es für mich einfacher ist, nur einen Benchmark-Fall für mich selbst zu erstellen, aber ich habe mich entschlossen, hier eine Frage zu stellen, falls sich jemand über dieselbe Frage Gedanken gemacht hat oder falls es jemand im Voraus weiß.

Was ist der schnellste Weg, dies in C # zu tun?

EDIT

Ich habe den Benchmark gepostet hier . Auf den ersten Blick scheint Richards Weg der schnellste zu sein. Seine Art und Weise und auch die von Marc würden jedoch aufgrund des falschen Regex-Musters nichts tun. Nach dem Korrigieren des Musters von

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

zu

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

es sieht so aus, als ob der alte Weg mit verketteten .Replace () - Aufrufen der schnellste ist

24

Vielen Dank für Ihre Inputs. Ich habe einen schnellen und schmutzigen Benchmark geschrieben, um Ihre Inputs zu testen. Ich habe das Parsen von 4 Strings mit 500.000 Iterationen getestet und vier Durchgänge gemacht. Das Ergebnis ist wie folgt:

 *** Pass 1 
 Alter (verketteter String. Platz ()) in 814 ms abgeschlossen 
 Logicnp (ToCharArray) in 916 ms 
 Oleksii ( StringBuilder) fertiggestellt in 943 ms 
 André Christoffer Andersen (Lambda mit Aggregat) fertiggestellt in 2551 ms 
 Richard (Regex mit MatchEvaluator) vollendet in 215 ms 
 Marc Gravell (Static Regex) abgeschlossen in 1008 ms 
 
 *** Pass 2 
 Alter (verketteter String.Replace ()) in 786 ms abgeschlossen 
 Logicnp ( ToCharArray) Weg in 920 ms 
 Oleksii (StringBuilder) Weg in 905 ms 
 Fertiggestellt. André Christoffer Andersen (Lambda mit Aggregat) Weg in 2515 ms 
 Richard (Regex w/MatchEvaluator) abgeschlossen in 217 ms 
 Marc Gravell (Static Regex) abgeschlossen in 1025 ms 
 
 *** Pass 3 
 Old (Chained String.Replace ( )) Weg abgeschlossen in 775 ms 
 logicnp (ToCharArray) Weg abgeschlossen in 903 ms 
 oleksii (StringBuilder) Weg abgeschlossen in 931 ms 
 André Christoffer Andersen (Lambda mit Aggregat) abgeschlossen in 2529 ms 
 Richard (Regex mit MatchEvaluator) abgeschlossen in 214 ms 
 Marc Gravell (Static Regex) abgeschlossen in 1022 ms [. .____.] 
 *** Pass 4 
 Alter (verketteter String.Replace ()) Weg in 799 ms 
 Logicnp (ToCharArray) Weg in 908 ms [.____. .] oleksii (StringBuilder) Weg in 938 ms 
 André Christoffer Andersen (Lambda mit Aggregat) in 2592 ms 
 Richard (Regex mit MatchEvaluator) in 225 ms 
 Marc Gravell (Static Regex) Weg in 1050 ms fertiggestellt 

Der Code für diesen Benchmark ist unten. Bitte überprüfen Sie den Code und bestätigen Sie, dass @Richard den schnellsten Weg hat. Beachten Sie, dass ich nicht geprüft habe, ob die Ausgaben korrekt waren.

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

der schnellste Weg

Die einzige Möglichkeit ist, die Leistung selbst zu vergleichen. Versuchen Sie es wie im Q, indem Sie StringBuilder und auch Regex.Replace verwenden.

Mikro-Benchmarks berücksichtigen jedoch nicht den Umfang des gesamten Systems. Wenn diese Methode nur einen kleinen Bruchteil des Gesamtsystems ausmacht, ist ihre Leistung für die Gesamtanwendung der Anwendung wahrscheinlich unwichtig.

Einige Notizen:

  1. Wenn Sie String wie oben (ich nehme an) verwenden, werden viele Zwischenzeichenfolgen erstellt: Mehr Arbeit für den GC. Aber es ist einfach.
  2. Durch die Verwendung von StringBuilder können dieselben zugrunde liegenden Daten bei jedem Ersetzen geändert werden. Dadurch entsteht weniger Müll. Es ist fast so einfach wie String.
  3. Die Verwendung einer regex ist am komplexesten (weil Sie Code benötigen, um die Ersetzung zu ermitteln), aber einen einzelnen Ausdruck zulässt. Ich würde erwarten, dass dies langsamer ist, es sei denn, die Liste der Ersetzungen ist sehr groß und Ersetzungen sind selten in der Eingabezeichenfolge (dh die meisten Ersetzmethodenaufrufe ersetzen nichts, sie kosten nur eine Suche durch die Zeichenfolge).

Ich gehe davon aus, dass # 2 bei wiederholter Verwendung (tausende Male) aufgrund der geringeren GC-Belastung etwas schneller wäre.

Für den Regex-Ansatz benötigen Sie Folgendes:

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!");
  }
});

Dies könnte auf wiederverwendbare Weise durch Parametrieren mit einem Dictionary<char,char> erfolgen, um die Ersetzungen und die wiederverwendbaren MatchEvaluator zu speichern.

15
Richard

Versuche dies:

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

Eine mögliche Lösung ist die Verwendung einer StringBuilder-Klasse.

Sie können den Code zunächst in eine einzelne Methode umwandeln

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

Sie können Lambda-Ausdrücke dafür verwenden, indem Sie Aggregate auf einer Zeichentabelle verwenden:

  //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]));
  }

Sie können dies wie folgt ausführen:

  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 = "[email protected]||[email protected]|[email protected]";

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

      //out: myString = "asgjkDDAdfsgPPjshdDDfAjgsldDkjPrhgunfhDADnsdflngs"
  }
3

Nun, ich würde versuchen sowas machen:

    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]];
        });
    }

Dies hat eine einzige Stelle zum Verwalten (oben) und erstellt einen vorkompilierten Regexfür die Ersetzung. Der gesamte Aufwand wird nur für einen einzigen ausgeführt (daher staticname__).

2
Marc Gravell

Hybrid StringBuilder-Ansatz mit 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