[C#] 문자열에서 특수 문자를 제거하는 가장 효율적인 방법

문자열에서 모든 특수 문자를 제거하고 싶습니다. 허용되는 문자는 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 자 정도의 짧은 길이입니다.



답변

왜 당신의 방법이 비효율적이라고 생각합니까? 실제로 가장 효율적인 방법 중 하나입니다.

물론 문자를 로컬 변수로 읽거나 열거자를 사용하여 배열 액세스 수를 줄여야합니다.

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.5ms
내 제안 된 변경 사항 : 47.1 ms.
StringBuilder 용량을 설정 한 광산 : 43.3ms
정규식 : 294.4ms

편집 2 : 위 코드에서 AZ와 az의 차이점을 추가했습니다. (성능 테스트를 다시 실행했으며 눈에 띄는 차이는 없습니다.)

편집 3 :
lookup + char [] 솔루션을 테스트했으며 약 13ms에서 실행됩니다.

물론 지불해야 할 가격은 거대한 조회 테이블을 초기화하고 메모리에 유지하는 것입니다. 글쎄, 그것은 그렇게 많은 데이터는 아니지만 그런 사소한 기능을위한 것입니다 …

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


답변

글쎄, 당신이 정말로 당신의 기능에서 성능을 짜낼 필요가 없다면, 유지하고 이해하기 가장 쉬운 것을 따라 가십시오. 정규식은 다음과 같습니다.

추가 성능을 위해 사전 컴파일하거나 첫 번째 호출에서 컴파일하도록 지시 할 수 있습니다 (이후의 호출은 더 빠릅니다).

public static string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}


답변

간단한 조회 테이블을 만드는 것이 좋습니다.이 테이블은 정적 생성자에서 초기화하여 문자 조합을 유효하게 설정할 수 있습니다. 이를 통해 빠른 단일 검사를 수행 할 수 있습니다.

편집하다

또한 속도를 높이기 위해 StringBuilder의 용량을 입력 문자열의 길이로 초기화하려고합니다. 재 할당을 피할 수 있습니다. 이 두 가지 방법을 함께 사용하면 속도와 유연성이 모두 향상됩니다.

다른 편집

컴파일러가 최적화 할 수 있다고 생각하지만 효율성뿐만 아니라 스타일 측면에서도 foreach를 권장합니다.


답변

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

그러나 성능이 매우 중요한 경우 “정규 경로”를 선택하기 전에 몇 가지 벤치 마크를 수행하는 것이 좋습니다.


답변

동적 문자 목록을 사용하는 경우 LINQ는 훨씬 빠르고 우아한 솔루션을 제공 할 수 있습니다.

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

이 접근 방식을 이전의 “빠른”접근 방법 두 가지 (릴리스 컴파일)와 비교했습니다.

  • LukeH의 문자 배열 솔루션-427ms
  • StringBuilder 솔루션-429ms
  • LINQ (이 답변)-98ms

알고리즘은 약간 수정되었습니다. 문자는 하드 코딩되지 않고 배열로 전달되므로 약간 영향을 줄 수 있습니다 (즉, 다른 솔루션은 문자 배열을 확인하기 위해 내부 foor 루프가 있음).

LINQ where 절을 사용하여 하드 코딩 된 솔루션으로 전환하면 결과는 다음과 같습니다.

  • 문자 배열 솔루션-7ms
  • StringBuilder 솔루션-22ms
  • LINQ-60ms

문자 목록을 하드 코딩하는 대신보다 일반적인 솔루션을 작성하려는 경우 LINQ 또는 수정 된 접근 방식을 살펴볼 가치가 있습니다. LINQ는 Regex보다 간결하고 읽기 쉬운 코드를 제공합니다.


답변

알고리즘이 효율적이라고 확신하지는 않습니다. O (n)이며 각 문자를 한 번만 봅니다. 값을 확인하기 전에 마술처럼 값을 알지 않는 한 그 이상을 얻지 못할 것입니다.

그러나 나는 당신의 용량을 StringBuilder문자열의 초기 크기로 초기화 할 것 입니다. 귀하의 인식 성능 문제가 메모리 재 할당에서 비롯된 것 같습니다.

참고 : 점검 Az안전하지 않습니다. 당신은 포함하고 [, \, ], ^, _,와`…

참고 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;
}