Эффективное решение проблемы String.Replace? - PullRequest
4 голосов
/ 27 октября 2009

Скажем, у меня есть массив строк, объявленных так:

String[] strings = new String[ 1024 ];

И учитывая массив пар ключ-значение, объявленный так:

KeyValuePair<String, String>[] pairs = new KeyValuePair<String, String>[ 50 ];

Каковы возможные обходные пути для решения, которое сделает это:

for ( int i = 0; i < 1024; ++i )
{
  foreach ( var kv in pairs )
  {
      strings[ i ] = strings[ i ].Replace( kv.Key, kv.Value );
  }
}

Этот код является произвольным и предназначен только для демонстрации реальной проблемы. Учитывая много значений ключа, которые известны во время компиляции, как я могу сделать эффективный String.Replace и, возможно, уменьшить вызовы метода и копировать String вокруг (каждый вызов String.Replace будет производить новую неизменяемую String, а не очень эффективно при наличии большого количества таких заменяющих вызовов)?

Ответы [ 5 ]

2 голосов
/ 27 октября 2009

Это не помогает с количеством циклов, но если вы используете StringBuilder в качестве промежуточного звена, он вызывает .Replace с тем же набором сигнатур параметров.

Edit:

Не уверен, что это быстрее, но вы можете использовать Regex.Replace с делегатом оценщика.

Если вы строите поисковое регулярное выражение с вашими ключами: (Key1 | key2 | ключ3 | Key4 ...)

и затем передав делегата в .Replace, вы можете вернуть поиск на основе свойства Match в Value.

  public string ReplaceData(Match m)
  {
      return pairs[m.Value];         
  }

...

  pairs.Add("foo","bar");
  pairs.Add("homer","simpson");
  Regex r = new Regex("(?>foo|homer)");
  MatchEvaluator myEval = new MatchEvaluator(class.ReplaceData);
  string sOutput = r.Replace(sInput, myEval);
2 голосов
/ 27 октября 2009

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

1 голос
/ 28 октября 2009

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

Посмотрите на этот код:

class Program {

    static void Main(String[] args) {

        // Filling the data
        List<KeyValuePair<String, String>> map = new List<KeyValuePair<String, String>>();
        List<StringBuilder> strings = new List<StringBuilder>();
        List<StringBuilder> strings2 = new List<StringBuilder>();

        for (Int32 i = 0; i < 50; i++) {
            String key = String.Format("[KEY{0}]", i);
            String value = String.Format("Text of KEY{0}", i);
            KeyValuePair<String, String> keyValuePair = new KeyValuePair<String, String>(key, value);
            map.Add(keyValuePair);
        }

        for (Int32 i = 0; i < 1024; i++) {
            StringBuilder text = new StringBuilder();

            foreach (KeyValuePair<String, String> keyValuePair in map) {
                text.AppendFormat("Some text before - {0} - Some text after.", keyValuePair.Key);
                text.AppendLine();
            }

            strings.Add(text);
            strings2.Add(text);
        }


        // Measuring the normal loop
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        foreach (StringBuilder text in strings) {
            foreach (KeyValuePair<String, String> eachMap in map) {
                text.Replace(eachMap.Key, eachMap.Value);
            }
        }

        stopwatch.Stop();

        Console.WriteLine("Time with normal loop: {0}", stopwatch.Elapsed);


        // Measuring the parallel loop
        stopwatch.Reset();
        stopwatch.Start();

        Parallel.ForEach(strings2, text => {
            foreach (KeyValuePair<String, String> eachMap in map) {
                text.Replace(eachMap.Key, eachMap.Value);
            }
        });

        stopwatch.Stop();

        Console.WriteLine("Time with parallel: {0}", stopwatch.Elapsed);
        Console.ReadLine();
    }
}

И посмотрите на некоторые меры, выполняемые на моем ноутбуке (AMD Turion64 X2 - 2 ядра):



Время с нормальной петлей: 00: 00: 03.5956428
Время с параллелью: 00: 00: 01.8707367

Время с нормальным циклом: 00: 00: 02.1467821
Время с параллелью: 00: 00: 01.4627365

Время с нормальной петлей: 00: 00: 03.4123084
Время с параллелью: 00: 00: 01.6704408



Надеюсь, это поможет.

Рикардо Ласерда Каштелу-Бранку

0 голосов
/ 28 октября 2009

Я думаю, что то, что предлагают Джо и Агент_9191, по сути одно и то же:

const int NUM_STRINGS = 1024;
string[] strings = new string[NUM_STRINGS];
StringBuilder[] stringBuilders = new StringBuilder[NUM_STRINGS];

// ensure that all StringBuilder objects are initialized
for (int i = 0; i < NUM_STRINGS; i++) {
    stringBuilders[i] = new StringBuilder(strings[i]);
}

KeyValuePair<string, string>[] pairs = new KeyValuePair<string, string>[50];

for (int i = 0; i < NUM_STRINGS; i++) {
    foreach (var kv in pairs) {
         // StringBuilder is mutable;
         // this actually modifies the instance
         stringBuilders[i].Replace(kv.Key, kv.Value);
    }

    strings[i] = stringBuilders[i].ToString();
}
0 голосов
/ 27 октября 2009

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

 String[] strings = new String[1024];
 KeyValuePair<String, String>[] pairs = new KeyValuePair<String, String>[ 50 ];

 String[] replaced = strings.Select(x => 
                                         pairs.Any( y => y.Key == x ) ? 
                                         pairs.Where( z => z.Key == x).Select( val =>  val.Value).First() :  x )
                            .ToArray();
...