Эффективная популяция шаблонов - PullRequest
9 голосов
/ 26 января 2012

Допустим, у меня есть текстовый шаблон с несколькими полями, которые необходимо заполнить:

var template = "hello {$name}. you are {$age} years old. you live in {$location}"

и IDictionary<string,string> значений для замены:

key     | value
===================
name    | spender
age     | 38
location| UK

Наивный способ заполнения шаблона может выглядеть примерно так:

var output = template;
foreach(var kvp in templValues)
{
    output = output.Replace(string.format("{{${0}}}", kvp.Key), kvp.Value);
}

Однако это кажется болезненно неэффективным. Есть ли лучший способ?

Ответы [ 5 ]

4 голосов
/ 26 января 2012

В вашем подходе нет ничего плохого, это зависит от используемого контекста. Например, в узком цикле критических задач это не самый эффективный подход, но для случайного использования или в графическом интерфейсе это, вероятно, нормально.

Более эффективным решением было бы проанализировать строку. Например. поиск первого {, а затем следующего }. Текст между ними является ключом для поиска, который вы можете затем заменить. Затем вы начинаете поиск с символа после }. Преимущество этого подхода заключается в том, что если введенное значение имеет встроенный токен, оно не будет заменено. Недостатком является то, что при разборе труднее обрабатывать крайние случаи.

4 голосов
/ 26 января 2012

Вы можете использовать Regex.Replace(), например:

var output = new Regex(@"\{\$([^}]+)\}").Replace(
    template,
    m => templValues.ContainsKey(m.Captures[1].Value)
        ? templValues[m.Captures[1].Value]
        : m.Value);

AFAIK, это также предотвратит непредвиденные результаты, если ваш словарь построен так, потому что это может привести к "hello UK. you are 38 years old. you live in UK", а также "hello {$location}. you are 38 years old. you live in UK", так как словари не сортируют свои ключи:

key     | value
===================
name    | {$location}
age     | 38
location| UK

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

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

1 голос
/ 26 января 2012

Используйте регулярное выражение, соответствующее спецификатору поля:

var fieldRegex = new Regex(@"{\$([^}]+?)}", RegexOptions.Compiled);

Объяснение регулярного выражения:

  1. литерал {
  2. литерал $(который должен быть экранирован)
  3. захваченная группа ( ), содержащая:
    1. не- } символов
    2. один или несколько из них +
    3. принимая как можно меньше ? (ленивый захват)
  4. литерал }

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

var template = "hello {$name}. you are {$age} years old. you live in {$location}";

var fieldValues = new Dictionary<string, string>
                        {
                            { "name", "spender" },
                            { "age", "38" },
                            { "location", "UK" },
                        };

var output = fieldRegex.Replace(
    template,
    match => fieldValues[match.Groups[1].Value]);

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

0 голосов
/ 26 января 2012

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

static string DictFormat(string template, IDictionary<string, string> dict) {

    const string left_delimiter = "{$";
    int left_delimiter_len = left_delimiter.Length;
    const string right_delimiter = "}";
    int right_delimiter_len = right_delimiter.Length;

    var sb = new StringBuilder();

    int end = 0;
    while (true) {

        int start = template.IndexOf(left_delimiter, end);
        if (start >= 0) {
            sb.Append(template.Substring(end, start - end));
            start += left_delimiter_len;
            end = template.IndexOf(right_delimiter, start);
            if (end >= 0) {
                string key = template.Substring(start, end - start);
                string value;
                if (dict.TryGetValue(key, out value)) {
                    sb.Append(value);
                    end += right_delimiter_len;
                }
                else
                    throw new ArgumentException(string.Format("Key not found: {0}", key), "template");
            }
            else
                throw new ArgumentException(string.Format("Key starting at {0} not properly closed.", start), "template");
        }
        else {
            sb.Append(template.Substring(end));
            return sb.ToString();
        }

    }

}

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

const string template = "hello {$name}. you are {$age} years old. you live in {$location}";
var dict = new Dictionary<string, string> { { "name", "spender" }, { "age", "38" }, { "location", "UK" } };
string result = DictFormat(template, dict);
0 голосов
/ 26 января 2012

С риском звучать глупо, вы можете просто написать функцию, которая возвращает желаемую строку:

public string CreateString(string name, string age, string location)
{
  return "hello " + name + ". you are " + age + " years old. you live in " + location;
}

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

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