Подтвердить что ты не робот

Более быстрый способ выполнения нескольких замен строк

Мне нужно сделать следующее:

    static string[] pats = { "å", "Å", "æ", "Æ", "ä", "Ä", "ö", "Ö", "ø", "Ø" ,"è", "È", "à", "À", "ì", "Ì", "õ", "Õ", "ï", "Ï" };
    static string[] repl = { "a", "A", "a", "A", "a", "A", "o", "O", "o", "O", "e", "E", "a", "A", "i", "I", "o", "O", "i", "I" };
    static int i = pats.Length;
    int j;

     // function for the replacement(s)
     public string DoRepl(string Inp) {
      string tmp = Inp;
        for( j = 0; j < i; j++ ) {
            tmp = Regex.Replace(tmp,pats[j],repl[j]);
        }
        return tmp.ToString();            
    }
    /* Main flow processes about 45000 lines of input */

Каждая строка имеет 6 элементов, которые проходят через DoRepl. Примерно 300 000 вызовов функций. Каждый из них имеет 20 Regex.Replace, всего 6 миллионов заменяет.

Есть ли более элегантный способ сделать это за меньшее количество проходов?

4b9b3361

Ответ 1

static Dictionary<char, char> repl = new Dictionary<char, char>() { { 'å', 'a' }, { 'ø', 'o' } }; // etc...
public string DoRepl(string Inp)
{
    var tmp = Inp.Select(c =>
    {
        char r;
        if (repl.TryGetValue(c, out r))
            return r;
        return c;
    });
    return new string(tmp.ToArray());
}

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

Ответ 2

Как насчет этого "трюка"?

string conv = Encoding.ASCII.GetString(Encoding.GetEncoding("Cyrillic").GetBytes(input));

Ответ 3

Без регулярного выражения это может быть быстрее.

    for( j = 0; j < i; j++ ) 
    {
        tmp = tmp.Replace(pats[j], repl[j]);
    }

Edit

Другой способ: Zip и StringBuilder:

StringBuilder result = new StringBuilder(input);
foreach (var zipped = patterns.Zip(replacements, (p, r) => new {p, r}))
{
  result = result.Replace(zipped.p, zipped.r);
}
return result.ToString();

Ответ 4

Во-первых, я бы использовал StringBuilder для выполнения перевода внутри буфера и не создавать новые строки повсюду.

Далее, в идеале нам бы хотелось что-то вроде XPath translate(), поэтому мы можем работать со строками вместо массивов или сопоставлений, Давайте сделаем это в методе расширения:

public static StringBuilder Translate(this StringBuilder builder,
    string inChars, string outChars)
{
    int length = Math.Min(inChars.Length, outChars.Length);
    for (int i = 0; i < length; ++i) {
        builder.Replace(inChars[i], outChars[i]);
    }
    return builder;
}

Затем используйте его:

StringBuilder builder = new StringBuilder(yourString);
yourString = builder.Translate("åÅæÆäÄöÖøØèÈàÀìÌõÕïÏ",
    "aAaAaAoOoOeEaAiIoOiI").ToString();

Ответ 5

Проблема с вашим исходным регулярным выражением заключается в том, что вы не используете его в полном объеме. Помните, что шаблон регулярного выражения может иметь чередование. Вам по-прежнему нужен словарь, но вы можете сделать это за один проход без прокрутки каждого символа.

Это будет достигнуто следующим образом:

string[] pats = { "å", "Å", "æ", "Æ", "ä", "Ä", "ö", "Ö", "ø", "Ø" ,"è", "È", "à", "À", "ì", "Ì", "õ", "Õ", "ï", "Ï" };
string[] repl = { "a", "A", "a", "A", "a", "A", "o", "O", "o", "O", "e", "E", "a", "A", "i", "I", "o", "O", "i", "I" };
// using Zip as a shortcut, otherwise setup dictionary differently as others have shown
var dict = pats.Zip(repl, (k,v) => new { Key = k, Value = v }).ToDictionary(o => o.Key, o => o.Value);

string input = "åÅæÆäÄöÖøØèÈàÀìÌõÕïÏ";
string pattern = String.Join("|", dict.Keys.Select(k => k)); // use ToArray() for .NET 3.5
string result = Regex.Replace(input, pattern, m => dict[m.Value]);

Console.WriteLine("Pattern: " + pattern);
Console.WriteLine("Input: " + input);
Console.WriteLine("Result: " + result);

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

Ответ 6

Если вы хотите удалить акценты, возможно, это решение было бы полезно Как удалить диакритические символы (акценты) из строки в .NET?

В противном случае я бы сделал это за один проход:

Dictionary<char, char> replacements = new Dictionary<char, char>();
...
StringBuilder result = new StringBuilder();
foreach(char c in str)
{
  char rc;
  if (!_replacements.TryGetValue(c, out rc)
  {
    rc = c;
  }
  result.Append(rc);
}

Ответ 7

Самый быстрый (IMHO) способ (сравнимый даже со словарем) в специальном случае замены символа "один к одному" - это полная карта символов:

public class Converter
{
    private readonly char[] _map;

    public Converter()
    {
        // This code assumes char to be a short unsigned integer
        _map = new char[char.MaxValue];

        for (int i = 0; i < _map.Length; i++)
            _map[i] = (char)i;

        _map['å'] = 'a';  // Note that 'å' is used as an integer index into the array.
        _map['Å'] = 'A';
        _map['æ'] = 'a';
        // ... the rest of overriding map
    }

    public string Convert(string source)
    {
        if (string.IsNullOrEmpty(source))
            return source;

        var result = new char[source.Length];

        for (int i = 0; i < source.Length; i++)
            result[i] = _map[source[i]]; // convert using the map

        return new string(result);
    }
}

Чтобы ускорить этот код, вы можете использовать "небезопасное" ключевое слово и использовать указатели. Таким образом, перемещение массива строк может выполняться быстрее и без привязки (что теоретически будет оптимизировано VM, но может и не быть).

Ответ 8

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