Заменить несколько строковых элементов в C #

86

Есть ли лучший способ сделать это ...

MyString.Trim().Replace("&", "and").Replace(",", "").Replace("  ", " ")
         .Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();

Я расширил класс String, чтобы сократить его до одной задачи, но есть ли более быстрый способ?

public static class StringExtension
{
    public static string clean(this string s)
    {
        return s.Replace("&", "and").Replace(",", "").Replace("  ", " ")
                .Replace(" ", "-").Replace("'", "").Replace(".", "")
                .Replace("eacute;", "é").ToLower();
    }
}

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

https://gist.github.com/ChrisMcKee/5937656

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

Крис Макки
источник
1
Судя по результатам ваших тестов, похоже, что словарная версия не выполняет всех замен, которые, как я подозреваю, делают ее быстрее, чем решения StringBuilder.
жаба
1
@toad Привет с 2009 года; В апреле я добавил комментарий об этой вопиющей ошибке. Суть обновлена, хотя я пропустил D. Версия словаря все еще быстрее.
Крис МакКи
Возможный дубликат альтернативы String.Replace несколько раз?
Tot Zam
1
@TotZam хотя бы проверяйте даты, прежде чем отмечать вещи; это с 2009 года, это с 2012 года
Крис Макки,
Поскольку многие ответы здесь, похоже, связаны с производительностью, я считаю, что следует указать, что ответ Андрея Адаманко, вероятно, будет самым быстрым для многих замен; безусловно, быстрее, чем цепочка .Replace (), особенно на большой входной строке, как указано в его ответе.
person27

Ответы:

123

Быстрее - нет. Эффективнее - да, если вы воспользуетесь StringBuilderклассом. В вашей реализации каждая операция генерирует копию строки, которая при определенных обстоятельствах может снизить производительность. Строки - это неизменяемые объекты, поэтому каждая операция просто возвращает измененную копию.

Если вы ожидаете, что этот метод будет активно вызываться для кратных Stringsзначительных длин, может быть лучше «перенести» его реализацию в StringBuilderкласс. С его помощью любая модификация выполняется непосредственно в этом экземпляре, поэтому вы избавляетесь от ненужных операций копирования.

public static class StringExtention
{
    public static string clean(this string s)
    {
        StringBuilder sb = new StringBuilder (s);

        sb.Replace("&", "and");
        sb.Replace(",", "");
        sb.Replace("  ", " ");
        sb.Replace(" ", "-");
        sb.Replace("'", "");
        sb.Replace(".", "");
        sb.Replace("eacute;", "é");

        return sb.ToString().ToLower();
    }
}
BC2
источник
2
Для наглядности ответ по словарю - самый быстрый stackoverflow.com/a/1321366/52912
Крис Макки,
3
В вашем тесте на gist.github.com/ChrisMcKee/5937656 словарный тест не завершен: он не выполняет все замены и заменяет «», а не «». Не все замены могут быть причиной того, что он самый быстрый в тесте. Замена регулярного выражения также не завершена. Но самое главное, ваша строка TestData очень короткая. Подобно принятым состояниям ответа, строка должна быть значительной длины, чтобы StringBuilder имел преимущество. Не могли бы вы повторить тест со строками по 10, 100 и 1 МБ?
Лейф
Это хороший момент; в нынешнем виде он использовался для очистки URL-адресов, поэтому тестирование на 100 КБ - 1 МБ было бы нереалистичным. Я обновлю тест, чтобы он использовал все это, хотя это было ошибкой.
Крис Макки
Для лучшей производительности переберите символы и замените их самостоятельно. Однако это может быть утомительно, если у вас есть строки из более чем одного символа (их обнаружение заставляет вас сравнивать несколько символов одновременно, а их замена требует выделения дополнительной памяти и перемещения остальной части строки).
Хаим Фридман
13

это будет более эффективно:

public static class StringExtension
{
    public static string clean(this string s)
    {
        return new StringBuilder(s)
              .Replace("&", "and")
              .Replace(",", "")
              .Replace("  ", " ")
              .Replace(" ", "-")
              .Replace("'", "")
              .Replace(".", "")
              .Replace("eacute;", "é")
              .ToString()
              .ToLower();
    }
}
TheVillageIdiot
источник
Действительно трудно читать. Я уверен, что вы знаете, что он делает, но младший разработчик почесает в затылке, что на самом деле происходит. Я согласен - я также всегда ищу короткую руку, чтобы что-то написать - Но это было только для моего собственного удовольствия. Другие люди были в ужасе от кучи беспорядка.
Петр Кула
3
На самом деле это медленнее. BenchmarkOverhead ... 13 мс StringClean-user151323 ... 2843 мс StringClean-TheVillageIdiot ... 2921 мс Зависит от повторных запусков, но ответ побеждает gist.github.com/anonymous/5937596
Крис МакКи
12

Если вам просто нужно красивое решение и вам не нужно экономить несколько наносекунд, как насчет сахара LINQ?

var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };

var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));
TimS
источник
Подобно примеру C в Gist (если вы посмотрите выше, более уродливый оператор linq находится в комментарии)
Chris McKee
1
Интересно, что вы определяете функциональный статус как «уродливый», чем процедурный.
TimS
спорить не собираюсь; это просто предпочтение. Как вы говорите, linq - это просто синтаксический сахар; и, как я уже сказал, я уже поставил эквивалент над кодом :)
Крис Макки
11

Может чуть читабельнее?

    public static class StringExtension {

        private static Dictionary<string, string> _replacements = new Dictionary<string, string>();

        static StringExtension() {
            _replacements["&"] = "and";
            _replacements[","] = "";
            _replacements["  "] = " ";
            // etc...
        }

        public static string clean(this string s) {
            foreach (string to_replace in _replacements.Keys) {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

Также добавьте предложение New In Town о StringBuilder ...

Паоло Тедеско
источник
5
Это было бы более private static Dictionary<string, string> _replacements = new Dictionary<string, string>() { {"&", "and"}, {",", ""}, {" ", " "} /* etc */ };
читаемо так
2
или, конечно ... частный статический только для чтения Dictionary <string, string> Replacements = new Dictionary <string, string> () {{"&", "и"}, {",", ""}, {"", " " } /* так далее */ }; общедоступная статическая строка Clean (this string s) {return Replacements.Keys.Aggregate (s, (current, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee
2
-1: Использование словаря здесь не имеет значения. Просто используйте List<Tuple<string,string>>. Это также изменяет порядок выполнения замен, И не так быстро, как, например s.Replace("a").Replace("b").Replace("c"). Не используйте это!
Thomas
6

Есть одна вещь, которую можно оптимизировать в предлагаемых решениях. Наличие множества вызовов в Replace()заставляет код выполнять несколько проходов по одной и той же строке. С очень длинными строками решения могут работать медленно из-за нехватки емкости кэша ЦП. Может быть, стоит подумать о замене нескольких строк за один проход .

Андрей Адаменко
источник
1
Многие ответы, похоже, касаются производительности, и в этом случае это лучший вариант. И это просто, потому что это просто документированная перегрузка String.Replace, где вы возвращаете ожидаемое значение на основе совпадения, в этом примере, используя словарь для их сопоставления. Должно быть просто для понимания.
person27
4

Другой вариант использования linq -

[TestMethod]
public void Test()
{
  var input = "it's worth a lot of money, if you can find a buyer.";
  var expected = "its worth a lot of money if you can find a buyer";
  var removeList = new string[] { ".", ",", "'" };
  var result = input;

  removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));

  Assert.AreEqual(expected, result);
}
Луис Фелипе
источник
Вы можете объявить, а var removeList = new List<string> { /*...*/ };затем просто позвоните removeList.ForEach( /*...*/ );и упростите свой код. Также обратите внимание, что он не полностью отвечает на вопрос, потому что все найденные строки заменяются на String.Empty.
Tok
2

Я делаю нечто подобное, но в моем случае я выполняю сериализацию / десериализацию, поэтому мне нужно иметь возможность идти в обоих направлениях. Я считаю, что использование строки [] [] работает почти идентично словарю, включая инициализацию, но вы можете пойти и в другом направлении, возвращая заменители к их исходным значениям, для чего словарь действительно не настроен.

Изменить: вы можете использовать Dictionary<Key,List<Values>>для получения того же результата, что и строка [] []

sidDemure
источник
-1
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
    input = input.Replace(repl[i, 0], repl[i, 1]);
}
user7718176
источник
2
Вам следует подумать о добавлении контекста к своим ответам. Как краткое объяснение того, что он делает, И, если уместно, почему вы написали это именно так.
Нил