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

Две опции:

  • Возврат default(T), что означает, что Вы возвратитесь null, если T будет ссылочным типом (или nullable тип значения), 0 для int, '\0' для char, и т.д. ( таблица Значений по умолчанию (Ссылка C#) )
  • , Ограничивают T, чтобы быть ссылочным типом с where T : class ограничение и затем возвратиться null как нормальный

255
задан BartoszKP 24 September 2013 в 09:42
поделиться

13 ответов

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

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

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);
}
311
ответ дан 23 November 2019 в 02:46
поделиться

Если вас беспокоит скорость, используйте указатели для редактирования существующих строка. Вы можете закрепить строку и получить указатель на нее, а затем запустить цикл 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
}
-2
ответ дан 23 November 2019 в 02:46
поделиться

For S&G's, Linq-ified way:

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

I don't think this is going to be the most efficient way, however.

1
ответ дан 23 November 2019 в 02:46
поделиться

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

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

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

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

1
ответ дан 23 November 2019 в 02:46
поделиться

Я не уверен, что ваш алгоритм неэффективен. Это 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;
}
5
ответ дан 23 November 2019 в 02:46
поделиться

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

StringBuilder sb = new StringBuilder(str.Length);
3
ответ дан 23 November 2019 в 02:46
поделиться
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);
}
12
ответ дан 23 November 2019 в 02:46
поделиться

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

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

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

11
ответ дан 23 November 2019 в 02:46
поделиться

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

edit

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

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

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

15
ответ дан 23 November 2019 в 02:46
поделиться

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

2
ответ дан 23 November 2019 в 02:46
поделиться

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

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

public static string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
183
ответ дан 23 November 2019 в 02:46
поделиться
StringBuilder sb = new StringBuilder();

for (int i = 0; i < fName.Length; i++)
{
   if (char.IsLetterOrDigit(fName[i]))
    {
       sb.Append(fName[i]);
    }
}
4
ответ дан 23 November 2019 в 02:46
поделиться
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
ответ дан 23 November 2019 в 02:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: