Более быстрый способ выполнения нескольких замен строк
Мне нужно сделать следующее:
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 миллионов заменяет.
Есть ли более элегантный способ сделать это за меньшее количество проходов?
Ответы
Ответ 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, но большинство движков регулярных выражений имеют транслитерированную операцию, которая будет работать здесь хорошо. Тогда вам понадобится только один вызов в строке.