Самый эффективный способ удалить специальные символы из строки

267

Я хочу удалить все специальные символы из строки. Допустимые символы: AZ (верхний или нижний регистр), цифры (0-9), подчеркивание (_) или знак точки (.).

У меня есть следующее, это работает, но я подозреваю (я знаю!), Что это не очень эффективно:

    public static string RemoveSpecialCharacters(string str)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.Length; i++)
        {
            if ((str[i] >= '0' && str[i] <= '9')
                || (str[i] >= 'A' && str[i] <= 'z'
                    || (str[i] == '.' || str[i] == '_')))
                {
                    sb.Append(str[i]);
                }
        }

        return sb.ToString();
    }

Какой самый эффективный способ сделать это? Как будет выглядеть регулярное выражение, и как оно будет сравниваться с обычными манипуляциями со строками?

Строки, которые будут очищены, будут довольно короткими, обычно длиной от 10 до 30 символов.

Оби-Ван Кеноби
источник
5
Я не буду помещать это в ответ, так как он не будет более эффективным, но есть ряд статических методов типа char, таких как char.IsLetterOrDigit (), которые вы можете использовать в своем операторе if, чтобы сделать его хотя бы более разборчивым.
Мартин Харрис
5
Я не уверен, что проверка от A до z безопасна, так как вводит 6 символов, которые не являются алфавитными, только один из которых является желательным (подчеркивание).
Стивен Судит
4
Сосредоточьтесь на том, чтобы сделать ваш код более читабельным. если вы не делаете это в цикле, как 500 раз в секунду, эффективность не имеет большого значения. Используйте регулярное выражение, и его будет намного легче читать.
Байрон Уитлок
4
Байрон, вы, вероятно, правы в том, что вам нужно подчеркнуть удобочитаемость. Тем не менее, я скептически отношусь к тому, что регулярные выражения читаются. :-)
Стивен Судит
2
Регулярные выражения, читаемые или нет, похожи на то, что немецкий читается или нет; это зависит от того, знаете ли вы это или нет (хотя в обоих случаях вы будете время от времени сталкиваться с грамматическими правилами, которые не имеют смысла;)
Blixt

Ответы:

325

Почему вы думаете, что ваш метод не эффективен? На самом деле это один из самых эффективных способов сделать это.

Конечно, вы должны прочитать символ в локальную переменную или использовать перечислитель, чтобы уменьшить количество обращений к массиву:

public static string RemoveSpecialCharacters(this string str) {
   StringBuilder sb = new StringBuilder();
   foreach (char c in str) {
      if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_') {
         sb.Append(c);
      }
   }
   return sb.ToString();
}

Одна вещь, которая делает такой метод эффективным, это то, что он хорошо масштабируется. Время выполнения будет зависеть от длины строки. Там нет неприятных сюрпризов, если вы будете использовать его на большой строке.

Редактировать:
я сделал быстрый тест производительности, выполняя каждую функцию по миллиону строк из 24 символов. Вот результаты:

Исходная функция: 54,5 мс.
Мое предлагаемое изменение: 47,1 мс.
Мой с настройкой StringBuilder емкость: 43,3 мс.
Регулярное выражение: 294,4 мс

Редактировать 2: я добавил различие между AZ и az в коде выше. (Я повторно тест производительности, и нет никакой заметной разницы.)

Редактировать 3:
я протестировал решение lookup + char [], и оно работает примерно за 13 мс.

Цена, которую нужно заплатить, - это, конечно, инициализация огромной таблицы поиска и ее сохранение в памяти. Ну, это не так много данных, но это много для такой тривиальной функции ...

private static bool[] _lookup;

static Program() {
   _lookup = new bool[65536];
   for (char c = '0'; c <= '9'; c++) _lookup[c] = true;
   for (char c = 'A'; c <= 'Z'; c++) _lookup[c] = true;
   for (char c = 'a'; c <= 'z'; c++) _lookup[c] = true;
   _lookup['.'] = true;
   _lookup['_'] = true;
}

public static string RemoveSpecialCharacters(string str) {
   char[] buffer = new char[str.Length];
   int index = 0;
   foreach (char c in str) {
      if (_lookup[c]) {
         buffer[index] = c;
         index++;
      }
   }
   return new string(buffer, 0, index);
}
Guffa
источник
4
Я согласен. Единственное другое изменение, которое я хотел бы сделать, это добавить начальный аргумент емкости в конструктор StringBuilder, "= new StringBuilder (str.Length)".
Дэвид
2
Мой ответ, с использованием char[]буфера, а не StringBuilder, имеет небольшое преимущество по этому тестированию. (Мой менее читабельный, поэтому небольшое преимущество в производительности, вероятно, не стоит.)
Луки,
1
@ Steven: Это вполне может иметь место, но тесты говорят сами за себя! В моих тестах использование char[]буфера работает (немного) лучше, чем StringBuilderдаже при масштабировании до строк длиной в десятки тысяч символов.
ЛукиH
10
@ downvoter: Почему downvote? Если вы не объясните, что считаете неправильным, это не улучшит ответ.
Guffa
2
@ ТИХАЯ: Нет, это не так, но вы должны сделать это только один раз. Если вы выделяете такой большой массив каждый раз, когда вызываете метод (и если вы часто вызываете метод), то этот метод становится самым медленным и вызывает много работы для сборщика мусора.
Гуффа
196

Ну, если вам действительно не нужно выжимать производительность из вашей функции, просто делайте то, что проще всего поддерживать и понимать. Регулярное выражение будет выглядеть так:

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

public static string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
Blixt
источник
1
Я предполагаю, что это, вероятно, достаточно сложный запрос, который будет быстрее, чем подход OP, особенно если он предварительно скомпилирован. Однако у меня нет доказательств, подтверждающих это. Это должно быть проверено. Если он не будет значительно медленнее, я бы выбрал этот подход независимо от того, как его легче читать и поддерживать. +1
rmeador
6
Это очень простое регулярное выражение (нет возврата или каких-либо сложных вещей там), поэтому он должен быть чертовски быстрым.
9
@ rmeador: без компиляции он примерно в 5 раз медленнее, скомпилированный в 3 раза медленнее, чем его метод. Тем не менее, в 10 раз проще :-D
user7116
6
Регулярные выражения не являются волшебными молотками и никогда не быстрее, чем оптимизированный вручную код.
Кристиан Клаузер
2
Для тех, кто помнит знаменитую цитату Кнута об оптимизации, это то, с чего начать. Затем, если вы обнаружите, что вам нужна дополнительная тысячная доля миллисекунды, воспользуйтесь одним из других приемов.
Джон
15

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

редактировать

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

другое редактирование

Я думаю, что компилятор мог бы оптимизировать его, но в качестве стиля и эффективности я рекомендую использовать foreach вместо for.

Стивен Судит
источник
Для массивов forи foreachвыдачи аналогичного кода. Я не знаю о строках, хотя. Я сомневаюсь, что JIT знает о массивоподобной природе String.
Кристиан Клаузер
1
Могу поспорить, что JIT знает больше о массивоподобной природе строки, чем ваша [удаленная шутка]. Андерс Этал проделал большую работу по оптимизации всего, что касается строк в .net
Я сделал это, используя HashSet <char>, и это примерно в 2 раза медленнее, чем его метод. Использование bool [] едва ли быстрее (0,0469мс / iter против 0,0559мс / iter) по сравнению с версией, которую он имеет в OP ... с проблемой того, что он менее читабелен.
user7116
1
Я не видел никакой разницы в производительности между использованием массива bool и массива int. Я бы использовал массив bool, так как он сокращает таблицу поиска с 256 кб до 64 кб, но для такой тривиальной функции по-прежнему много данных ... И это только на 30% быстрее.
Гуффа
1
@Guffa 2) Учитывая, что мы храним только буквенно-цифровые символы и несколько символов базовой латиницы, нам нужна таблица только для младшего байта, поэтому размер на самом деле не является проблемой. Если мы хотим быть универсальными, то стандартная техника Unicode - это двойное косвенное обращение. Другими словами, таблица из 256 ссылок на таблицы, многие из которых указывают на одну и ту же пустую таблицу.
Стивен Судит
12
public static string RemoveSpecialCharacters(string str)
{
    char[] buffer = new char[str.Length];
    int idx = 0;

    foreach (char c in str)
    {
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
            || (c >= 'a' && c <= 'z') || (c == '.') || (c == '_'))
        {
            buffer[idx] = c;
            idx++;
        }
    }

    return new string(buffer, 0, idx);
}
LukeH
источник
1
+1, протестировано и примерно на 40% быстрее, чем StringBuilder. 0,0294мс / строка против 0,0399мс / строка
user7116
Просто чтобы быть уверенным, вы имеете в виду StringBuilder с или без предварительного выделения?
Стивен Судит
С предварительным распределением это все еще на 40% медленнее, чем распределение char [] и новая строка.
user7116
2
Мне это нравится. Я настроил этот методforeach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Крис Марисик
11

Регулярное выражение будет выглядеть так:

public string RemoveSpecialChars(string input)
{
    return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}

Но если производительность очень важна, я рекомендую вам сделать несколько тестов, прежде чем выбрать «путь регулярных выражений» ...

CMS
источник
11

Если вы используете динамический список символов, LINQ может предложить гораздо более быстрое и изящное решение:

public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
    return new String(value.Except(specialCharacters).ToArray());
}

Я сравнил этот подход с двумя из предыдущих «быстрых» подходов (компиляция релиза):

  • Решение массива символов от LukeH - 427 мс
  • Решение StringBuilder - 429 мс
  • LINQ (этот ответ) - 98 мс

Обратите внимание, что алгоритм немного изменен - ​​символы передаются в виде массива, а не жестко закодированы, что может немного повлиять на вещи (т. Е. / Другие решения будут иметь внутренний цикл foor для проверки массива символов).

Если я перейду к жестко запрограммированному решению, используя предложение LINQ where, то получится следующее:

  • Решение с массивом символов - 7мс
  • Решение StringBuilder - 22 мс
  • LINQ - 60 мс

Возможно, стоит взглянуть на LINQ или модифицированный подход, если вы планируете написать более общее решение, а не жестко программировать список символов. LINQ определенно дает вам краткий, хорошо читаемый код - даже больше, чем Regex.

ShadowChaser
источник
3
Этот подход выглядит хорошо, но он не работает - Except () является операцией над множествами, поэтому вы получите только первое появление каждого уникального символа в строке.
McKenzieG1
5

Я не уверен, что ваш алгоритм не эффективен. Это O (n) и смотрит на каждого персонажа только один раз. Вы не получите ничего лучше, если не будете волшебным образом знать значения перед их проверкой.

Однако я бы инициализировал емкость вашего StringBuilderк начальному размеру строки. Я предполагаю, что ваша проблема производительности связана с перераспределением памяти.

Примечание: проверка A- zне безопасно. Вы в том числе [, \, ], ^, _, и `...

Примечание для стороны 2: Для этого дополнительного бита эффективности сравните порядок сравнений, чтобы минимизировать количество сравнений. (В худшем случае вы говорите о 8 сравнениях, поэтому не думайте слишком усердно.) Это меняется в зависимости от вашего ожидаемого вклада, но один пример может быть:

if (str[i] >= '0' && str[i] <= 'z' && 
    (str[i] >= 'a' || str[i] <= '9' ||  (str[i] >= 'A' && str[i] <= 'Z') || 
    str[i] == '_') || str[i] == '.')

Примечание 3: Если по какой-либо причине вам ДЕЙСТВИТЕЛЬНО нужно, чтобы это было быстро, оператор switch может быть быстрее. Компилятор должен создать таблицу переходов для вас, что приведет только к одному сравнению:

switch (str[i])
{
    case '0':
    case '1':
    .
    .
    .
    case '.':
        sb.Append(str[i]);
        break;
}
ЖХ.
источник
1
Я согласен, что вы не можете победить O (N) на этом. Тем не менее, существует цена за сравнение, которая может быть снижена. Поиск в таблице имеет низкую фиксированную стоимость, в то время как ряд сравнений будет увеличиваться по мере добавления новых исключений.
Стивен Судит
Что касается примечания 3, действительно ли вы думаете, что таблица переходов будет быстрее, чем поиск таблиц?
Стивен Судит
Я провел быстрый тест производительности на коммутаторе, и он выполняет те же функции, что и сравнение.
Гуффа
@ Стивен Судит - Рискну предположить, что они примерно одинаковы. Хотите запустить тест?
жк.
7
O (n) обозначения иногда меня бесят. Люди будут делать глупые предположения, основываясь на том факте, что алгоритм уже O (n). Если бы мы изменили эту подпрограмму, чтобы заменить вызовы str [i] на функцию, которая извлекала значение сравнения путем создания одноразового SSL-соединения с сервером на противоположной стороне мира ... вы чертовски уверены, что увидите огромную производительность Разница и алгоритм все еще O (n). Стоимость O (1) для каждого алгоритма значительна и НЕ эквивалентна!
Даррон
4
StringBuilder sb = new StringBuilder();

for (int i = 0; i < fName.Length; i++)
{
   if (char.IsLetterOrDigit(fName[i]))
    {
       sb.Append(fName[i]);
    }
}
Чамика Сандамал
источник
4

Вы можете использовать регулярное выражение следующим образом:

return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));
Джованни Фарто М.
источник
3

Мне кажется, это хорошо. Единственное улучшение, которое я хотел бы сделать, - это инициализация StringBuilderс длиной строки.

StringBuilder sb = new StringBuilder(str.Length);
Бруно Конде
источник
3

Я согласен с этим примером кода. Единственное другое это я превращаю в метод расширения строкового типа. Так что вы можете использовать его в очень простой строке или коде:

string test = "abc@#$123";
test.RemoveSpecialCharacters();

Спасибо Guffa за ваш эксперимент.

public static class MethodExtensionHelper
    {
    public static string RemoveSpecialCharacters(this string str)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in str)
            {
                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }
}
Tola Ch.
источник
2

Я бы использовал String Replace с Regular Expression для поиска «специальных символов», заменяя все найденные символы пустой строкой.

Стивен Райтон
источник
+1, конечно, меньше кода и, возможно, более читаемый, игнорируя Regex с однократной записью.
Кенни
1
@ Кенни - я согласен. В первоначальном вопросе даже говорится, что строки короткие - 10-30 символов. Но, видимо, многие все еще думают, что мы продаем процессорное время с точностью до секунды ...
Том Бушелл
Reguler expressin работает так лениво. Так что его не следует использовать всегда.
RockOnGom
2

Я должен был сделать что-то подобное для работы, но в моем случае мне пришлось отфильтровать все, что не является буквой, цифрой или пробелом (но вы могли легко изменить это в соответствии с вашими потребностями). Фильтрация выполняется на стороне клиента в JavaScript, но по соображениям безопасности я также выполняю фильтрацию на стороне сервера. Поскольку я могу ожидать, что большинство строк будут чистыми, я хотел бы избежать копирования строки, если в этом нет особой необходимости. Это позволит перейти к реализации ниже, которая должна работать лучше как для чистых, так и для грязных строк.

public static string EnsureOnlyLetterDigitOrWhiteSpace(string input)
{
    StringBuilder cleanedInput = null;
    for (var i = 0; i < input.Length; ++i)
    {
        var currentChar = input[i];
        var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar);

        if (charIsValid)
        {
            if(cleanedInput != null)
                cleanedInput.Append(currentChar);
        }
        else
        {
            if (cleanedInput != null) continue;
            cleanedInput = new StringBuilder();
            if (i > 0)
                cleanedInput.Append(input.Substring(0, i));
        }
    }

    return cleanedInput == null ? input : cleanedInput.ToString();
}
Даниэль Бланкенштайнер
источник
1

Для S & G, Linq-ified способ:

var original = "(*^%foo)(@)&^@#><>?:\":';=-+_";
var valid = new char[] { 
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 
    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 
    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', 
    '9', '0', '.', '_' };
var result = string.Join("",
    (from x in original.ToCharArray() 
     where valid.Contains(x) select x.ToString())
        .ToArray());

Однако я не думаю, что это будет самый эффективный способ.


источник
2
Это не так, потому что это линейный поиск.
Стивен Судит
1
public string RemoveSpecial(string evalstr)
{
StringBuilder finalstr = new StringBuilder();
            foreach(char c in evalstr){
            int charassci = Convert.ToInt16(c);
            if (!(charassci >= 33 && charassci <= 47))// special char ???
             finalstr.append(c);
            }
return finalstr.ToString();
}
Шико
источник
1

Использование:

s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

bool my_predicate(char c)
{
 return !(isalpha(c) || c=='_' || c==' '); // depending on you definition of special characters
}

И вы получите чистую строку s.

erase()удалит его из всех специальных символов и легко настраивается с помощью my_predicate()функции.

Бхавья Агарвал
источник
1

HashSet is O (1)
Не уверен, что это быстрее, чем существующее сравнение

private static HashSet<char> ValidChars = new HashSet<char>() { 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '_' };
public static string RemoveSpecialCharacters(string str)
{
    StringBuilder sb = new StringBuilder(str.Length / 2);
    foreach (char c in str)
    {
        if (ValidChars.Contains(c)) sb.Append(c);
    }
    return sb.ToString();
}

Я проверил, и это не быстрее, чем принятый ответ.
Я оставлю это так, как будто вам нужен настраиваемый набор символов, это было бы хорошим решением.

папараццо
источник
Почему вы думаете, что сравнение не O (1)?
Гуффа
@ Гуффа Я не уверен, что это не так, и я удалил свой комментарий. И +1. Я должен был сделать больше тестирования, прежде чем сделать комментарий.
Папараццо
1

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

Кроме этого, вы должны инициализировать StringBuilder с ожидаемой длиной, чтобы промежуточную строку не нужно было копировать по мере роста.

Хорошее число - это длина исходной строки или немного меньше (в зависимости от характера входных данных функций).

Наконец, вы можете использовать справочную таблицу (в диапазоне 0..127), чтобы узнать, должен ли символ быть принят.

Кристиан Клаузер
источник
Регулярное выражение уже проверено, и оно примерно в пять раз медленнее. С таблицей поиска в диапазоне 0..127 вам все равно придется проверять код символа перед использованием таблицы поиска, поскольку символы представляют собой 16-битные значения, а не 7-битные значения.
Гуффа
@ Гуффа ... да? ;)
Кристиан Клаузер
1

Следующий код имеет следующий вывод (вывод заключается в том, что мы также можем сэкономить некоторые ресурсы памяти, выделяя массив меньшего размера):

lookup = new bool[123];

for (var c = '0'; c <= '9'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'A'; c <= 'Z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'a'; c <= 'z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

48: 0  
49: 1  
50: 2  
51: 3  
52: 4  
53: 5  
54: 6  
55: 7  
56: 8  
57: 9  
65: A  
66: B  
67: C  
68: D  
69: E  
70: F  
71: G  
72: H  
73: I  
74: J  
75: K  
76: L  
77: M  
78: N  
79: O  
80: P  
81: Q  
82: R  
83: S  
84: T  
85: U  
86: V  
87: W  
88: X  
89: Y  
90: Z  
97: a  
98: b  
99: c  
100: d  
101: e  
102: f  
103: g  
104: h  
105: i  
106: j  
107: k  
108: l  
109: m  
110: n  
111: o  
112: p  
113: q  
114: r  
115: s  
116: t  
117: u  
118: v  
119: w  
120: x  
121: y  
122: z  

Вы также можете добавить следующие строки кода для поддержки русской локали (размер массива будет 1104):

for (var c = 'А'; c <= 'Я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'а'; c <= 'я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
Павел Шклейник
источник
1

Я не уверен, что это самый эффективный способ, но он работает для меня

 Public Function RemoverTildes(stIn As String) As String
    Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD)
    Dim sb As New StringBuilder()

    For ich As Integer = 0 To stFormD.Length - 1
        Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich))
        If uc <> UnicodeCategory.NonSpacingMark Then
            sb.Append(stFormD(ich))
        End If
    Next
    Return (sb.ToString().Normalize(NormalizationForm.FormC))
End Function
RonaldPaguay
источник
Ответ делает работу, но вопрос был для C #. (PS: я знаю, что это было практически пять лет назад, но все же ..) Я использовал Telerik VB в C # Converter, (и наоборот), и код работал просто отлично - хотя не уверен ни в ком другом. (Другое дело, converter.telerik.com )
Моморо
1

Здесь предлагается множество предлагаемых решений, некоторые из которых более эффективны, чем другие, но, возможно, не очень читабельны. Вот тот, который может быть не самым эффективным, но, безусловно, пригодным для использования в большинстве ситуаций, и он довольно лаконичен и удобен для чтения, используя Linq:

string stringToclean = "This is a test.  Do not try this at home; you might get hurt. Don't believe it?";

var validPunctuation = new HashSet<char>(". -");

var cleanedVersion = new String(stringToclean.Where(x => (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());
Стив Файвишевский
источник
-1
public static string RemoveSpecialCharacters(string str){
    return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}
Jawaid
источник
1
Боюсь, replaceAllэто не функция C # String, а либо Java, либо JavaScript
Csaba Toth
-1
public static string RemoveAllSpecialCharacters(this string text) {
  if (string.IsNullOrEmpty(text))
    return text;

  string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
  return result;
}
Hasan_H
источник
Ответ неверный. Если вы собираетесь использовать регулярное выражение, оно должно быть инклюзивным, а не эксклюзивным, потому что вы пропускаете некоторых персонажей. На самом деле, уже есть ответ с регулярным выражением. И чтобы быть полным - регулярное выражение МЕДЛЕННО, тогда функция прямого сравнения символов.
TPAKTOPA
-3

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

static void Main(string[] args)
{
    string str = "string!$%with^&*invalid!!characters";
    Console.WriteLine( str ); //print original string
    FixMyString( str, ' ' );
    Console.WriteLine( str ); //print string again to verify that it has been modified
    Console.ReadLine(); //pause to leave command prompt open
}


public static unsafe void FixMyString( string str, char replacement_char )
{
    fixed (char* p_str = str)
    {
        char* c = p_str; //temp pointer, since p_str is read-only
        for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well
            if (!IsValidChar(*c)) //check whether the current character is invalid
                (*c) = replacement_char; //overwrite character in existing string with replacement character
    }
}

public static bool IsValidChar( char c )
{
    return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.' || c == '_');
    //return char.IsLetterOrDigit( c ) || c == '.' || c == '_'; //this may work as well
}
Triynko
источник
14
Noooooooooo! Изменение строки в .NET - это BAAAAAAAAAAAAD! Все в фреймворке опирается на правило, что строки неизменны, и если вы нарушите их, вы можете получить очень удивительные побочные эффекты ...
Гуффа