Замінити кілька рядкових елементів на C #


86

Чи є кращий спосіб зробити це ...

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

Я розширив клас рядків, щоб звести його до одного завдання, але чи є швидший спосіб?

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. Версія словника все-таки швидша.
Chris McKee


1
@TotZam принаймні перевіряйте дати, перш ніж позначати речі; це з 2009 року, з 2012 року
Кріс Маккі

Оскільки тут багато відповідей, здається, стосуються ефективності, я вважаю, що слід зазначити, що відповідь Андрія Адаманка, швидше за все, буде найшвидшою для багатьох замін; звичайно швидше, ніж ланцюжок. Замінити (), особливо на великому вхідному рядку, як зазначено у його відповіді.
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();
    }
}

2
Для наочності словникова відповідь - це найшвидший stackoverflow.com/a/1321366/52912
Chris McKee

3
У вашому тесті на gist.github.com/ChrisMcKee/5937656 перевірка словника не завершена: він не виконує всі заміни, а "" замінює ", а не" ". Не всі заміни можуть бути причиною того, чому це найшвидше в еталоні. Заміна регулярного виразу також не завершена. Але найголовніше, що ваш рядок TestData дуже короткий. Як і прийняті стани відповіді, рядок повинен мати значну довжину, щоб StringBuilder мав перевагу. Не могли б ви повторити еталон із рядками 10kB, 100kB та 1MB?
Лейф

Це хороший момент; як воно зараз використовується для очищення URL-адрес, тому тестування на 100 Кб - 1 Мб було б нереально. Я оновлюю еталон, тому, використовуючи все це, хоча це було помилкою.
Chris McKee

Для кращої роботи прокрутіть символи та замініть їх самостійно. Однак це може бути нудно, якщо у вас є більше, ніж однострочні рядки (знайдіть, це змушує вас порівнювати кілька символів одночасно, тоді як їх заміна вимагає виділення більше пам'яті та переміщення решти рядка).
Хаїм Фрідман,

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();
    }
}

Дійсно важко читати. Я впевнений, ви знаєте, що це робить, але молодший розробник почухає собі голову від того, що насправді відбувається. Я погоджуюсь - я також завжди шукаю короткі руки, щоб щось написати - Але це було лише для мого власного задоволення. Інші люди шалели від купи безладу.
Piotr Kula

3
Це насправді повільніше. BenchmarkOverhead ... 13ms StringClean-user151323 ... 2843ms StringClean-TheVillageIdiot ... 2921ms Залежить від повторів, але відповідь виграє gist.github.com/anonymous/5937596
Chris McKee

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));

Подібно до прикладу С у суті (якщо ви подивитесь вище, у коментарі є більш потворне посилання)
Кріс Маккі

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
або звичайно ... приватний статичний словник лише для читання <рядок, рядок> Заміни = новий словник <рядок, рядок> () {{"&", "та"}, {",", ""}, {"", ""} / * тощо * /}; загальнодоступний статичний рядок Clean (цей рядок s) {return Replacements.Keys.Aggregate (s, (current, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee

2
-1: Використання словника не робить тут ніякого сенсу. Просто використовуйте a List<Tuple<string,string>>. Це також змінює порядок заміни, що приймається І не так швидко, як, наприклад s.Replace("a").Replace("b").Replace("c"). Не використовуйте це!
Томас

6

Є одне, що може бути оптимізовано у запропонованих рішеннях. Наявність багатьох дзвінків Replace()змушує код робити кілька проходів по одному рядку. За дуже довгих рядків рішення можуть бути повільними через помилки в обсязі кеш-пам'яті процесора. Можливо, слід подумати про заміну кількох рядків за один прохід .


1
Багато відповідей, здається, стурбовані ефективністю, і в цьому випадку це найкраще. І це просто, оскільки це просто задокументоване перевантаження String. Замініть, де ви повертаєте очікуване значення на основі відповідності, у цьому прикладі, використовуючи словник для їх відповідності. Це має бути просто для розуміння.
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.
Ток,

2

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

Редагувати: Ви можете використовувати Dictionary<Key,List<Values>>для того, щоб отримати той самий результат, що і рядок [] []


-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]);
}

2
Вам слід подумати про додавання контексту до своїх відповідей. Як коротке пояснення того, що він робить, і, якщо це доречно, чому ви написали це так, як писали.
Ніл,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.