2016-12-16 2 views
3

Я работаю с довольно большим набором строк, которые мне нужно обрабатывать как можно быстрее.C# performance - Regex vs. multiple Split

Формат довольно исправлено:

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

или

[name]/[type]:[key] 

Я надеюсь, что мое представление в порядке. Это означает, что у меня есть слово (в моем случае я называю это Name), затем косой чертой, затем следуют другое слово (я называю это Type), затем двоеточие, и за ним следует либо разделенный запятыми список пары ключ-значение (key=value) или один ключ.

Name, Type не может содержать никаких пробелов, однако поля могут быть key и value.

В настоящее время я использую Regex для синтаксического анализа этих данных, и раскол:

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

Теперь я знаю, что это не самый необязательный случай, но я не уверен, что будет самым быстрым:

  • Split строка сначала по : затем с помощью / для первого элемента, и , для второго, а затем обработать последний список и раскол снова =
  • Используйте текущую смесь, как это
  • Используйте полностью регулярное выражение на основе

Конечно, я открыт для предложений, моя главная цель состоит в том, чтобы достичь быстрой обработки этой одной строки.

+0

Если ключи или значения могут иметь ':' или '/', вы должны придерживаться регулярного выражения. –

+3

_ «Моя главная цель - добиться самой быстрой обработки этой единственной строки» - так ** измерить ** различные подходы. Вы можете использовать класс Stopwatch для этого: лучше всего сделать это в конфигурации Release, а также запустить тесты несколько раз и использовать средний результат. – stuartd

+1

Регулярное выражение обычно не является оптимальным решением, поскольку оно построено для общих случаев использования (общий код обычно не является оптимальным). Возможно, вам придется подумать о написании собственного кода для этого. –

ответ

0

Его всегда интересно реализовать пользовательский парсер. Очевидно, что в отношении обслуживания кода Regex, вероятно, является лучшим выбором, но если производительность является главной задачей, то вам, вероятно, нужен парсерный синтаксический анализатор, который даже в простейших синтаксисах работает намного больше.

Я быстро взломал один из них (это может быть немного хакерским в некоторых местах), чтобы увидеть, что потребуется, чтобы реализовать его с некоторым базовым исправлением ошибок и информацией. Это не проверено никоим образом, но мне было бы любопытно, если бы он был минимально функциональным, чтобы знать, насколько хорошо он складывается с решением Regex с точки зрения производительности.

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