Две опции:
default(T)
, что означает, что Вы возвратитесь null
, если T будет ссылочным типом (или nullable тип значения), 0
для int
, '\0'
для char
, и т.д. ( таблица Значений по умолчанию (Ссылка C#) ) where T : class
ограничение и затем возвратиться null
как нормальный Как вы думаете, почему ваш метод неэффективен? На самом деле это один из наиболее эффективных способов сделать это.
Вы, конечно, должны считывать символ в локальную переменную или использовать перечислитель, чтобы уменьшить количество обращений к массиву:
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);
}
Если вас беспокоит скорость, используйте указатели для редактирования существующих строка. Вы можете закрепить строку и получить указатель на нее, а затем запустить цикл 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
}
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.
Интересно, работает ли замена на основе Regex (возможно, скомпилированная) быстрее? Придется проверить, что Кто-то обнаружил, что это примерно в 5 раз медленнее.
Кроме этого, вы должны инициализировать StringBuilder ожидаемой длиной, чтобы промежуточная строка не была копируется по мере роста.
Хорошее число - это длина исходной строки или что-то немного меньшее (в зависимости от характера входных данных функций).
Наконец, вы можете использовать таблицу поиска (в диапазоне 0..127), чтобы узнать, следует ли принимать символ.
Я не уверен, что ваш алгоритм неэффективен. Это 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;
}
Мне это кажется хорошим. Единственное улучшение, которое я хотел бы сделать, - это инициализировать StringBuilder
длиной строки.
StringBuilder sb = new StringBuilder(str.Length);
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);
}
Регулярное выражение будет выглядеть так:
public string RemoveSpecialChars(string input)
{
return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}
Но если производительность очень важна, я рекомендую вам выполнить несколько тестов, прежде чем выбирать «путь регулярного выражения» ...
Я предлагаю создать простую таблицу поиска, которую вы можете инициализировать в статическом конструкторе, чтобы установить допустимую комбинацию символов. Это позволяет вам выполнить быструю однократную проверку.
edit
Кроме того, для скорости вы захотите инициализировать емкость вашего StringBuilder, равную длине вашей входной строки. Это позволит избежать перераспределения. Эти два метода вместе дадут вам скорость и гибкость.
другое редактирование
Я думаю, что компилятор мог бы оптимизировать его, но с точки зрения стиля и эффективности я рекомендую foreach вместо for.
Я бы использовал замену строки регулярным выражением для поиска «специальных символов», заменяя все найденные символы пустой строкой.
Что ж, если вам действительно не нужно снижать производительность вашей функции, просто выберите то, что легче всего поддерживать и понимать. Регулярное выражение будет выглядеть следующим образом:
Для повышения производительности вы можете либо предварительно скомпилировать его, либо просто указать ему компилироваться при первом вызове (последующие вызовы будут быстрее.)
public static string RemoveSpecialCharacters(string str)
{
return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fName.Length; i++)
{
if (char.IsLetterOrDigit(fName[i]))
{
sb.Append(fName[i]);
}
}
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();
}