2016-12-16 7 views
3

Ich arbeite mit einer ziemlich großen Reihe von Strings, die ich so schnell wie möglich verarbeiten muss.C# -Performance - Regex vs. multiple Split

Das Format ist ziemlich festgelegt:

[name]/[type]:[list ([key] = [value],)] 

oder

[name]/[type]:[key] 

Ich hoffe, meine Darstellung in Ordnung ist. Was das bedeutet ist, dass ich ein Wort habe (in meinem Fall nenne ich es Name), dann einen Schrägstrich, gefolgt von einem anderen Wort (ich nenne es Type), dann einen Doppelpunkt, und es folgt entweder eine kommagetrennte Liste von Schlüssel-Wert-Paare (key=value) oder ein einzelner Schlüssel.

Name, Type kann keine Leerzeichen enthalten, aber die key und value Felder können.

Derzeit Regex Ich verwende diese Daten zu analysieren, und eine Spaltung:

var regex = @"(\w+)\/(\w+):(.*)"; 
var r = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Singleline); 
var m = r.Match(Id); 
if (m.Success) { 
    Name = m.Groups[1].Value; 
    Type= m.Groups[2].Value; 

    foreach (var intern in m.Groups[3].Value.Split(',')) 
    { 
     var split = intern.Trim().Split('='); 
     if (split.Length == 2) 
      Items.Add(split[0], split[1]); 
     else if (split.Length == 1) 
      Items.Add(split[0], split[0]); 
    } 
} 

Jetzt weiß ich dies nicht der Fall ist optional, aber ich bin nicht sicher, was die schnellste sein würde:

  • Split die Zeichenfolge zuerst vom : dann durch / für das erste Element, und , für die zweite, verarbeiten diese dann Liste und Split wieder durch =
  • die aktuelle Mischung verwenden, wie es
  • Nutzung ist ein völlig regex-basierte

Natürlich ist offen für Vorschläge sind, mein Hauptziel ist es, die schnellste Verarbeitung dieser Einzelsaite zu erreichen.

+0

Wenn die Schlüssel oder Werte ':' oder '/' haben können, sollten Sie sich an die Regex halten. –

+3

_ "Mein Hauptziel ist es, die schnellste Verarbeitung dieser einzelnen Saite zu erreichen" _ - so ** measure ** die verschiedenen Ansätze. Sie können dafür die Stoppuhr-Klasse verwenden: Am besten in der Release-Konfiguration, um die Tests mehrmals auszuführen und das durchschnittliche Ergebnis zu verwenden. – stuartd

+1

Regex ist normalerweise keine optimale Lösung, da es für allgemeine Anwendungsfälle (allgemeiner Code ist normalerweise nicht optimal) gebaut wird. Sie müssen möglicherweise in Erwägung ziehen, Ihren eigenen Code zu schreiben, um dies zu tun –

Antwort

0

Es macht immer Spaß, einen benutzerdefinierten Parser zu implementieren. Offensichtlich in Bezug auf die Codepflege ist Regex wahrscheinlich die beste Wahl, aber wenn die Leistung ein ultimatives Anliegen ist, dann brauchen Sie wahrscheinlich einen maßgeschneiderten Parser, der selbst in den einfachsten Syntaxen viel mehr Arbeit macht.

Ich habe eine sehr schnell ausgepeitscht (es könnte an einigen Stellen ein wenig hackish sein), um zu sehen, was es braucht, um ein mit einigen grundlegenden Fehlerkorrekturen und Informationen zu implementieren. Dies wird nicht in irgendeiner Weise getestet, aber ich wäre neugierig, wenn es minimal funktional ist, zu wissen, wie gut es mit der Regex-Lösung in Bezug auf die Leistung stapelt.

public class ParserOutput 
{ 
    public string Name { get; } 
    public string Type { get; } 
    public IEnumerable<Tuple<string, string>> KeyValuePairs { get; } 
    public bool ContainsKeyValuePairs { get; } 
    public bool HasErrors { get; } 
    public IEnumerable<string> ErrorDescriptions { get; } 

    public ParserOutput(string name, string type, IEnumerable<Tuple<string, string>> keyValuePairs, IEnumerable<string> errorDescriptions) 
    { 
     Name = name; 
     Type = type; 
     KeyValuePairs = keyValuePairs; 
     ContainsKeyValuePairs = keyValuePairs.FirstOrDefault()?.Item2?.Length > 0; 
     ErrorDescriptions = errorDescriptions; 
     HasErrors = errorDescriptions.Any(); 
    } 
} 

public class CustomParser 
{ 
    private const char forwardSlash = '/'; 
    private const char colon = ':'; 
    private const char space = ' '; 
    private const char equals = '='; 
    private const char comma = ','; 

    StringBuilder buffer = new StringBuilder(); 

    public ParserOutput Parse(string input) 
    { 
     var diagnosticsBag = new Queue<string>(); 

     using (var enumerator = input.GetEnumerator()) 
     { 
      var name = ParseToken(enumerator, forwardSlash, diagnosticsBag); 
      var type = ParseToken(enumerator, colon, diagnosticsBag); 
      var keyValuePairs = ParseListOrKey(enumerator, diagnosticsBag); 

      if (name.Length == 0) 
      { 
       diagnosticsBag.Enqueue("Input has incorrect format. Name could not be parsed."); 
      } 

      if (type.Length == 0) 
      { 
       diagnosticsBag.Enqueue("Input has incorrect format. Type could not be parsed."); 
      } 

      if (!keyValuePairs.Any() || 
       input.Last() == comma /*trailing comma is error?*/) 
      { 
       diagnosticsBag.Enqueue("Input has incorrect format. Key/Value pairs could not be parsed."); 
      } 

      return new ParserOutput(name, type, keyValuePairs, diagnosticsBag); 
     } 
    } 

    private string ParseToken(IEnumerator<char> enumerator, char separator, Queue<string> diagnosticsBag) 
    { 
     buffer.Clear(); 
     var allowWhitespaces = separator != forwardSlash && separator != colon; 

     while (enumerator.MoveNext()) 
     { 
      if (enumerator.Current == space && !allowWhitespaces) 
      { 
       diagnosticsBag.Enqueue($"Input has incorrect format. {(separator == forwardSlash ? "Name" : "Type")} cannot contain whitespaces."); 
      } 
      else if (enumerator.Current != separator) 
      { 
       buffer.Append(enumerator.Current); 
      } 
      else 
       return buffer.ToString(); 
     } 

     return buffer.ToString(); 
    } 

    private IEnumerable<Tuple<string, string>> ParseListOrKey(IEnumerator<char> enumerator, Queue<string> diagnosticsBag) 
    { 
     buffer.Clear(); 
     var isList = false; 

     while (true) 
     { 
      var key = ParseToken(enumerator, equals, diagnosticsBag); 
      var value = ParseToken(enumerator, comma, diagnosticsBag); 

      if (key.Length == 0) 
       break; 

      yield return new Tuple<string, string>(key, value); 

      if (!isList && value.Length != 0) 
      { 
       isList = true; 
      } 
      else if (isList && value.Length == 0) 
      { 
       diagnosticsBag.Enqueue($"Input has incorrect format: malformed [key/value] list."); 
      } 
     } 
    } 
}