Как я могу ускорить этот цикл? Есть ли класс для замены нескольких терминов одновременно? - PullRequest
7 голосов
/ 19 марта 2011

Цикл:

var pattern = _dict[key];
string before;
do
{
    before = pattern;
    foreach (var pair in _dict)
        if (key != pair.Key)
            pattern = pattern.Replace(string.Concat("{", pair.Key, "}"), string.Concat("(", pair.Value, ")"));
} while (pattern != before);
return pattern;

Он просто повторяет поиск и замену нескольких ключей. Словарь просто <string,string>.

Я вижу 2 улучшения в этом.

  1. Каждый раз, когда мы делаем pattern.Replace, он снова начинает поиск с начала строки. Было бы лучше, если бы, когда он нажал первый {, он просто просмотрел список ключей на совпадение (возможно, с использованием бинарного поиска), а затем заменил соответствующий.
  2. Бит pattern != before - это то, как я проверяю, было ли что-то заменено во время этой итерации. Если бы функция pattern.Replace вернула, сколько или если какая-либо замена произошла, мне бы это не понадобилось.

Однако ... Я действительно не хочу писать большой неприятный класс для всего этого. Это должно быть довольно распространенный сценарий? Существуют ли какие-либо решения?


Полный класс

Благодаря Элиану Эббингу и ChrisWue .

class FlexDict : IEnumerable<KeyValuePair<string,string>>
{
    private Dictionary<string, string> _dict = new Dictionary<string, string>();
    private static readonly Regex _re = new Regex(@"{([_a-z][_a-z0-9-]*)}", RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public void Add(string key, string pattern)
    {
        _dict[key] = pattern;
    }

    public string Expand(string pattern)
    {
        pattern = _re.Replace(pattern, match =>
            {
                string key = match.Groups[1].Value;

                if (_dict.ContainsKey(key))
                    return "(" + Expand(_dict[key]) + ")";

                return match.Value;
            });

        return pattern;
    }

    public string this[string key]
    {
        get { return Expand(_dict[key]); }
    }

    public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
    {
        foreach (var p in _dict)
            yield return new KeyValuePair<string,string>(p.Key, this[p.Key]);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Пример использования

class Program
{
    static void Main(string[] args)
    {
        var flex = new FlexDict
            {
                {"h", @"[0-9a-f]"},
                {"nonascii", @"[\200-\377]"},
                {"unicode", @"\\{h}{1,6}(\r\n|[ \t\r\n\f])?"},
                {"escape", @"{unicode}|\\[^\r\n\f0-9a-f]"},
                {"nmstart", @"[_a-z]|{nonascii}|{escape}"},
                {"nmchar", @"[_a-z0-9-]|{nonascii}|{escape}"},
                {"string1", @"""([^\n\r\f\\""]|\\{nl}|{escape})*"""},
                {"string2", @"'([^\n\r\f\\']|\\{nl}|{escape})*'"},
                {"badstring1", @"""([^\n\r\f\\""]|\\{nl}|{escape})*\\?"},
                {"badstring2", @"'([^\n\r\f\\']|\\{nl}|{escape})*\\?"},
                {"badcomment1", @"/\*[^*]*\*+([^/*][^*]*\*+)*"},
                {"badcomment2", @"/\*[^*]*(\*+[^/*][^*]*)*"},
                {"baduri1", @"url\({w}([!#$%&*-\[\]-~]|{nonascii}|{escape})*{w}"},
                {"baduri2", @"url\({w}{string}{w}"},
                {"baduri3", @"url\({w}{badstring}"},
                {"comment", @"/\*[^*]*\*+([^/*][^*]*\*+)*/"},
                {"ident", @"-?{nmstart}{nmchar}*"},
                {"name", @"{nmchar}+"},
                {"num", @"[0-9]+|[0-9]*\.[0-9]+"},
                {"string", @"{string1}|{string2}"},
                {"badstring", @"{badstring1}|{badstring2}"},
                {"badcomment", @"{badcomment1}|{badcomment2}"},
                {"baduri", @"{baduri1}|{baduri2}|{baduri3}"},
                {"url", @"([!#$%&*-~]|{nonascii}|{escape})*"},
                {"s", @"[ \t\r\n\f]+"},
                {"w", @"{s}?"},
                {"nl", @"\n|\r\n|\r|\f"},

                {"A", @"a|\\0{0,4}(41|61)(\r\n|[ \t\r\n\f])?"},
                {"C", @"c|\\0{0,4}(43|63)(\r\n|[ \t\r\n\f])?"},
                {"D", @"d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?"},
                {"E", @"e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?"},
                {"G", @"g|\\0{0,4}(47|67)(\r\n|[ \t\r\n\f])?|\\g"},
                {"H", @"h|\\0{0,4}(48|68)(\r\n|[ \t\r\n\f])?|\\h"},
                {"I", @"i|\\0{0,4}(49|69)(\r\n|[ \t\r\n\f])?|\\i"},
                {"K", @"k|\\0{0,4}(4b|6b)(\r\n|[ \t\r\n\f])?|\\k"},
                {"L", @"l|\\0{0,4}(4c|6c)(\r\n|[ \t\r\n\f])?|\\l"},
                {"M", @"m|\\0{0,4}(4d|6d)(\r\n|[ \t\r\n\f])?|\\m"},
                {"N", @"n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n"},
                {"O", @"o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o"},
                {"P", @"p|\\0{0,4}(50|70)(\r\n|[ \t\r\n\f])?|\\p"},
                {"R", @"r|\\0{0,4}(52|72)(\r\n|[ \t\r\n\f])?|\\r"},
                {"S", @"s|\\0{0,4}(53|73)(\r\n|[ \t\r\n\f])?|\\s"},
                {"T", @"t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t"},
                {"U", @"u|\\0{0,4}(55|75)(\r\n|[ \t\r\n\f])?|\\u"},
                {"X", @"x|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\x"},
                {"Z", @"z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z"},
                {"Z", @"z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z"},

                {"CDO", @"<!--"},
                {"CDC", @"-->"},
                {"INCLUDES", @"~="},
                {"DASHMATCH", @"\|="},
                {"STRING", @"{string}"},
                {"BAD_STRING", @"{badstring}"},
                {"IDENT", @"{ident}"},
                {"HASH", @"#{name}"},
                {"IMPORT_SYM", @"@{I}{M}{P}{O}{R}{T}"},
                {"PAGE_SYM", @"@{P}{A}{G}{E}"},
                {"MEDIA_SYM", @"@{M}{E}{D}{I}{A}"},
                {"CHARSET_SYM", @"@charset\b"},
                {"IMPORTANT_SYM", @"!({w}|{comment})*{I}{M}{P}{O}{R}{T}{A}{N}{T}"},
                {"EMS", @"{num}{E}{M}"},
                {"EXS", @"{num}{E}{X}"},
                {"LENGTH", @"{num}({P}{X}|{C}{M}|{M}{M}|{I}{N}|{P}{T}|{P}{C})"},
                {"ANGLE", @"{num}({D}{E}{G}|{R}{A}{D}|{G}{R}{A}{D})"},
                {"TIME", @"{num}({M}{S}|{S})"},
                {"PERCENTAGE", @"{num}%"},
                {"NUMBER", @"{num}"},
                {"URI", @"{U}{R}{L}\({w}{string}{w}\)|{U}{R}{L}\({w}{url}{w}\)"},
                {"BAD_URI", @"{baduri}"},
                {"FUNCTION", @"{ident}\("},
            };

        var testStrings = new[] { @"""str""", @"'str'", "5", "5.", "5.0", "a", "alpha", "url(hello)", 
            "url(\"hello\")", "url(\"blah)", @"\g", @"/*comment*/", @"/**/", @"<!--", @"-->", @"~=",
            "|=", @"#hash", "@import", "@page", "@media", "@charset", "!/*iehack*/important"};

        foreach (var pair in flex)
        {
            Console.WriteLine("{0}\n\t{1}\n", pair.Key, pair.Value);
        }

        var sw = Stopwatch.StartNew();
        foreach (var str in testStrings)
        {
            Console.WriteLine("{0} matches: ", str);
            foreach (var pair in flex)
            {
                if (Regex.IsMatch(str, "^(" + pair.Value + ")$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
                    Console.WriteLine("  {0}", pair.Key);
            }
        }
        Console.WriteLine("\nRan in {0} ms", sw.ElapsedMilliseconds);
        Console.ReadLine();
    }
}

Назначение

Для построения сложных регулярных выражений, которые могут расширять друг друга. А именно я пытаюсь реализовать спецификацию css .

Ответы [ 4 ]

9 голосов
/ 19 марта 2011

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

В настоящее время у меня нет визуальной студии здесь, но я думаю, что это функционально эквивалентно вашему примеру кода:

var pattern = _dict[key];
bool isChanged = false;

do
{
    isChanged = false;

    pattern = Regex.Replace(pattern, "{([^}]+)}", match => {
        string matchKey = match.Groups[1].Value;

        if (matchKey != key && _dict.ContainsKey(matchKey))
        {
            isChanged = true;
            return "(" + _dict[matchKey] + ")";
        }

        return match.Value;
    });
} while (isChanged);

Могу ли я спросить вас, зачем вам нужно делать do / whileцикл?Может ли значение ключа в словаре снова содержать {placeholders}, которое необходимо заменить?Можете ли вы быть уверены, что не застряли в бесконечном цикле, где ключ "A" содержит "Blahblah {B}", а ключ "B" содержит "Blahblah {A}"?

Редактировать: дальнейшие улучшения будут:

  • Использование скомпилированного регулярного выражения.
  • Использование рекурсии вместо цикла (см. Комментарий ChrisWue ).
  • Использование _dict.TryGetValue(), как в код Guffa.

В итоге вы получите алгоритм O (n), где n - это размер вывода, так что вы не можете добиться гораздо большего, чем этот.

2 голосов
/ 19 марта 2011

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

var pattern = _dict[key];
bool replaced = false;
do {
  pattern = Regex.Replace(pattern, @"\{([^\}]+)\}", m => {
    string k = m.Groups[1].Value;
    string value;
    if (k != key && _dict.TryGetValue(k, out value) {
      replaced = true;
      return "(" + value + ")";
    } else {
      return "{" + k + "}";
    }
  });
} while (replaced);
return pattern;
1 голос
/ 19 марта 2011

Вы можете реализовать следующий алгоритм:

  1. Поиск { в исходной строке
  2. Скопировать все до { в StringBuilder
  3. Найти соответствие } (поиск выполняется с последней позиции поиска)
  4. Сравните значение между { и } с ключами в вашем словаре
    • Если оно совпадает, скопируйте вString Builder ( + Value + )
    • Иное копирование из исходной строки
  5. Если конец исходной строки не достигнут, перейдите к шагу 1
0 голосов
/ 19 марта 2011

Можете ли вы вообще использовать PLINQ?

Что-то вроде:

var keys = dict.KeyCollection.Where(k => k != key);
bool replacementMade = keys.Any();

foreach(var k in keys.AsParallel(), () => {replacement code})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...