Разбор строки в C # - PullRequest
       17

Разбор строки в C #

5 голосов
/ 03 мая 2010

Каков самый эффективный способ анализа строки C # в виде

"(params (abc 1.3)(sdc 2.0)(www 3.05)....)"

в структуру в виде

struct Params
{
  double abc,sdc,www....;
}

Спасибо

EDIT Структура всегда имеет одинаковые параметры (одинаковые имена, только двойные, известные во время компиляции) .. но порядок не предоставляется .. только одна структура за раз

Ответы [ 8 ]

3 голосов
/ 03 мая 2010
using System;

namespace ConsoleApplication1
{
    class Program
    {
        struct Params
        {
            public double abc, sdc;
        };

        static void Main(string[] args)
        {
            string s = "(params (abc 1.3)(sdc 2.0))";
            Params p = new Params();
            object pbox = (object)p; // structs must be boxed for SetValue() to work

            string[] arr = s.Substring(8).Replace(")", "").Split(new char[] { ' ', '(', }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < arr.Length; i+=2)
                typeof(Params).GetField(arr[i]).SetValue(pbox, double.Parse(arr[i + 1]));
            p = (Params)pbox;
            Console.WriteLine("p.abc={0} p.sdc={1}", p.abc, p.sdc);
        }
    }
}

Примечание: если бы вы использовали класс вместо struct, бокс / распаковка не был бы необходим.

2 голосов
/ 03 мая 2010

регулярное выражение может сделать работу за вас:

public Dictionary<string, double> ParseString(string input){
    var dict = new Dictionary<string, double>();
    try
    {
        var re = new Regex(@"(?:\(params\s)?(?:\((?<n>[^\s]+)\s(?<v>[^\)]+)\))");
        foreach (Match m in re.Matches(input))
            dict.Add(m.Groups["n"].Value, double.Parse(m.Groups["v"].Value));
    }
    catch
    {
        throw new Exception("Invalid format!");
    }
    return dict;
}

используйте это как:

string str = "(params (abc 1.3)(sdc 2.0)(www 3.05))";
var parsed = ParseString(str);

// parsed["abc"] would now return 1.3

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

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

2 голосов
/ 03 мая 2010

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

var input = "(params (abc 1.3)(sdc 2.0)(www 3.05)....)";
var tokens = input.Split('(');
var typeName = tokens[0];
//you'll need more than the type name (assembly/namespace) so I'll leave that to you
Type t = getStructFromType(typeName);
var obj = TypeDescriptor.CreateInstance(null, t, null, null);
for(var i = 1;i<tokens.Length;i++)
{
    var innerTokens = tokens[i].Trim(' ', ')').Split(' ');
    var fieldName = innerTokens[0];
    var value = Convert.ToDouble(innerTokens[1]);
    var field = t.GetField(fieldName);
    field.SetValue(obj, value);
}

этот простой подход, однако, требует хорошо соответствующей строки, иначе он будет плохо себя вести.

Если грамматика немного сложнее, например, nested (), тогда этот простой подход не будет работать.

вы можете попытаться использовать regEx, но для этого все еще требуется довольно простая грамматика, поэтому, если у вас сложная грамматика, ваш лучший выбор - настоящий парсер. Ирония проста в использовании, поскольку вы можете написать все это на простом c # (хотя некоторые знания BNF являются плюсом).

2 голосов
/ 03 мая 2010

Вам нужно поддерживать несколько структур? Другими словами, должно ли это быть динамичным; или вы знаете определение структуры во время компиляции?

Разбор строки с помощью регулярного выражения будет очевидным выбором.

Вот регулярное выражение, которое проанализирует ваш формат строки:

private static readonly Regex regParser = new Regex(@"^\(params\s(\((?<name>[a-zA-Z]+)\s(?<value>[\d\.]+)\))+\)$", RegexOptions.Compiled);

Выполнение этого регулярного выражения в строке даст вам две группы с именами "name" и "value". Свойство Captures каждой группы будет содержать имена и значения.

Если тип структуры неизвестен во время компиляции, вам нужно будет использовать отражение для заполнения полей.

Если вы хотите сгенерировать определение структуры во время выполнения, вам потребуется использовать Reflection для генерации типа; или вам нужно будет сгенерировать исходный код.

С какой частью у вас проблемы?

1 голос
/ 04 мая 2010

Вы могли бы подумать о том, чтобы выполнить лишь достаточное количество манипуляций со строками, чтобы ввод был похож на стандартные аргументы командной строки, а затем используйте готовый анализатор аргументов командной строки, такой как NDesk.Options , чтобы заполнить объект Params. Вы отказываетесь от некоторой эффективности, но вы делаете это из-за ремонтопригодности.

public Params Parse(string input)
{
    var @params = new Params();
    var argv = ConvertToArgv(input);
    new NDesk.Options.OptionSet
        {
            {"abc=", v => Double.TryParse(v, out @params.abc)},
            {"sdc=", v => Double.TryParse(v, out @params.sdc)},
            {"www=", v => Double.TryParse(v, out @params.www)}
        }
        .Parse(argv);

    return @params;
}

private string[] ConvertToArgv(string input)
{
    return input
        .Replace('(', '-')
        .Split(new[] {')', ' '});
}
0 голосов
/ 22 августа 2012

Вот подход «из коробки»: convert () в {} и [SPACE] в ":", затем используйте System.Web.Script.Serialization.JavaScriptSerializer.Deserialize

string s = "(params (abc 1.3)(sdc 2.0))"
  .Replace(" ", ":")
  .Replace("(", "{")
  .Replace(")","}"); 

return new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize(s);
0 голосов
/ 05 мая 2010

Я бы просто сделал базовый парсер рекурсивного спуска. Это может быть более общим, чем вы хотите, но ничто иное не будет намного быстрее.

0 голосов
/ 03 мая 2010

Вы хотите создать представление данных вашего определенного синтаксиса?

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

Бедный "лексер" для C #

...