[C#] 문자열을 바꾸는 가장 좋은 방법

방금 C # 2.0에서 문자열 반전 함수를 작성해야했습니다 (즉 LINQ를 사용할 수 없음).

public string Reverse(string text)
{
    char[] cArray = text.ToCharArray();
    string reverse = String.Empty;
    for (int i = cArray.Length - 1; i > -1; i--)
    {
        reverse += cArray[i];
    }
    return reverse;
}

개인적으로 나는 그 기능에 열중하지 않으며 더 좋은 방법이 있다고 확신합니다. 있습니까?



답변

public static string Reverse( string s )
{
    char[] charArray = s.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}


답변

여기에서 문자열을 "Les Mise\u0301rables"로 올바르게 뒤집는 해결책이 "selbare\u0301siM seL"있습니다. 이는 코드 단위 ( 등) 또는 심지어 코드 포인트 (대리 쌍에 특별한주의를 기울임)를 기반으로 한 대부분의 구현 결과와 마찬가지로 (강조의 위치에 유의 selbarésiM seL하지 않음) 처럼 렌더링되어야 selbaŕesiM seL합니다 Array.Reverse.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

public static class Test
{
    private static IEnumerable<string> GraphemeClusters(this string s) {
        var enumerator = StringInfo.GetTextElementEnumerator(s);
        while(enumerator.MoveNext()) {
            yield return (string)enumerator.Current;
        }
    }
    private static string ReverseGraphemeClusters(this string s) {
        return string.Join("", s.GraphemeClusters().Reverse().ToArray());
    }

    public static void Main()
    {
        var s = "Les Mise\u0301rables";
        var r = s.ReverseGraphemeClusters();
        Console.WriteLine(r);
    }
}

(및 실제 실행 예 : https://ideone.com/DqAeMJ )

그것은 그 이후로 존재했던 grapheme cluster iteration을 위해 .NET API를 사용 하지만,보기에서 약간 “숨겨진”것 같습니다.


답변

이것은 놀랍도록 까다로운 질문으로 판명되었습니다.

Array.Reverse는 기본적으로 코딩되어 있으며 유지 관리 및 이해가 매우 간단하므로 대부분의 경우 Array.Reverse를 사용하는 것이 좋습니다.

내가 테스트 한 모든 경우에 StringBuilder보다 성능이 우수한 것 같습니다.

public string Reverse(string text)
{
   if (text == null) return null;

   // this was posted by petebob as well 
   char[] array = text.ToCharArray();
   Array.Reverse(array);
   return new String(array);
}

Xor사용 하는 특정 문자열 길이에 대해 더 빠른 두 번째 방법이 있습니다 .

    public static string ReverseXor(string s)
    {
        if (s == null) return null;
        char[] charArray = s.ToCharArray();
        int len = s.Length - 1;

        for (int i = 0; i < len; i++, len--)
        {
            charArray[i] ^= charArray[len];
            charArray[len] ^= charArray[i];
            charArray[i] ^= charArray[len];
        }

        return new string(charArray);
    }

참고 전체 유니 코드 UTF16 문자 세트를 지원하려면 이것을 읽으십시오 . 대신 구현을 사용하십시오. 위의 알고리즘 중 하나를 사용하고 문자열을 통해 실행하여 문자를 뒤집은 후에 정리하면 더욱 최적화 할 수 있습니다.

다음은 StringBuilder, Array.Reverse 및 Xor 메서드 간의 성능 비교입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication4
{
    class Program
    {
        delegate string StringDelegate(string s);

        static void Benchmark(string description, StringDelegate d, int times, string text)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int j = 0; j < times; j++)
            {
                d(text);
            }
            sw.Stop();
            Console.WriteLine("{0} Ticks {1} : called {2} times.", sw.ElapsedTicks, description, times);
        }

        public static string ReverseXor(string s)
        {
            char[] charArray = s.ToCharArray();
            int len = s.Length - 1;

            for (int i = 0; i < len; i++, len--)
            {
                charArray[i] ^= charArray[len];
                charArray[len] ^= charArray[i];
                charArray[i] ^= charArray[len];
            }

            return new string(charArray);
        }

        public static string ReverseSB(string text)
        {
            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }
            return builder.ToString();
        }

        public static string ReverseArray(string text)
        {
            char[] array = text.ToCharArray();
            Array.Reverse(array);
            return (new string(array));
        }

        public static string StringOfLength(int length)
        {
            Random random = new Random();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                sb.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
            }
            return sb.ToString();
        }

        static void Main(string[] args)
        {

            int[] lengths = new int[] {1,10,15,25,50,75,100,1000,100000};

            foreach (int l in lengths)
            {
                int iterations = 10000;
                string text = StringOfLength(l);
                Benchmark(String.Format("String Builder (Length: {0})", l), ReverseSB, iterations, text);
                Benchmark(String.Format("Array.Reverse (Length: {0})", l), ReverseArray, iterations, text);
                Benchmark(String.Format("Xor (Length: {0})", l), ReverseXor, iterations, text);

                Console.WriteLine();
            }

            Console.Read();
        }
    }
}

결과는 다음과 같습니다.

26251 Ticks String Builder (Length: 1) : called 10000 times.
33373 Ticks Array.Reverse (Length: 1) : called 10000 times.
20162 Ticks Xor (Length: 1) : called 10000 times.

51321 Ticks String Builder (Length: 10) : called 10000 times.
37105 Ticks Array.Reverse (Length: 10) : called 10000 times.
23974 Ticks Xor (Length: 10) : called 10000 times.

66570 Ticks String Builder (Length: 15) : called 10000 times.
26027 Ticks Array.Reverse (Length: 15) : called 10000 times.
24017 Ticks Xor (Length: 15) : called 10000 times.

101609 Ticks String Builder (Length: 25) : called 10000 times.
28472 Ticks Array.Reverse (Length: 25) : called 10000 times.
35355 Ticks Xor (Length: 25) : called 10000 times.

161601 Ticks String Builder (Length: 50) : called 10000 times.
35839 Ticks Array.Reverse (Length: 50) : called 10000 times.
51185 Ticks Xor (Length: 50) : called 10000 times.

230898 Ticks String Builder (Length: 75) : called 10000 times.
40628 Ticks Array.Reverse (Length: 75) : called 10000 times.
78906 Ticks Xor (Length: 75) : called 10000 times.

312017 Ticks String Builder (Length: 100) : called 10000 times.
52225 Ticks Array.Reverse (Length: 100) : called 10000 times.
110195 Ticks Xor (Length: 100) : called 10000 times.

2970691 Ticks String Builder (Length: 1000) : called 10000 times.
292094 Ticks Array.Reverse (Length: 1000) : called 10000 times.
846585 Ticks Xor (Length: 1000) : called 10000 times.

305564115 Ticks String Builder (Length: 100000) : called 10000 times.
74884495 Ticks Array.Reverse (Length: 100000) : called 10000 times.
125409674 Ticks Xor (Length: 100000) : called 10000 times.

짧은 문자열의 경우 Xor가 더 빠를 수 있습니다.


답변

하나의 라이너를 따르는 것보다 LINQ (.NET Framework 3.5 이상)를 사용할 수 있다면 짧은 코드가 제공됩니다. 다음에 using System.Linq;액세스 할 수 있도록 추가 하는 것을 잊지 마십시오 Enumerable.Reverse.

public string ReverseString(string srtVarable)
{
    return new string(srtVarable.Reverse().ToArray());
}

노트:

  • Martin Niederl 에 따르면 가장 빠른 버전은 아닙니다 . 여기에서 가장 빠른 선택보다 5.7 배 느립니다.
  • 이 코드는 다른 많은 옵션과 마찬가지로 모든 종류의 다중 문자 조합을 완전히 무시하므로 이러한 문자를 포함 하지 않는 숙제 및 문자열로 사용을 제한하십시오 . 이러한 조합을 올바르게 처리하는 구현에 대해서는이 질문의 다른 답변 을 참조하십시오 .

답변

문자열에 유니 코드 데이터 (엄격하게 말하면 BMP가 아닌 문자)가 포함 된 경우 문자열을 되돌릴 때 높은 대리 코드 단위와 낮은 대리 코드 단위의 순서를 바꿀 수 없기 때문에 게시 된 다른 방법으로 인해 데이터가 손상됩니다. (이에 대한 자세한 내용은 내 블로그 에서 찾을 수 있습니다 .)

다음 코드 샘플은 BMP 이외 문자가 포함 된 문자열 (예 : “\ U00010380 \ U00010381″(Ugaritic Letter Alpa, Ugaritic Letter Beta))을 올바르게 뒤집습니다.

public static string Reverse(this string input)
{
    if (input == null)
        throw new ArgumentNullException("input");

    // allocate a buffer to hold the output
    char[] output = new char[input.Length];
    for (int outputIndex = 0, inputIndex = input.Length - 1; outputIndex < input.Length; outputIndex++, inputIndex--)
    {
        // check for surrogate pair
        if (input[inputIndex] >= 0xDC00 && input[inputIndex] <= 0xDFFF &&
            inputIndex > 0 && input[inputIndex - 1] >= 0xD800 && input[inputIndex - 1] <= 0xDBFF)
        {
            // preserve the order of the surrogate pair code units
            output[outputIndex + 1] = input[inputIndex];
            output[outputIndex] = input[inputIndex - 1];
            outputIndex++;
            inputIndex--;
        }
        else
        {
            output[outputIndex] = input[inputIndex];
        }
    }

    return new string(output);
}


답변

“반복하지 마십시오”라는 관심을 가지고 다음 해결책을 제시합니다.

public string Reverse(string text)
{
   return Microsoft.VisualBasic.Strings.StrReverse(text);
}

VB.NET에서 기본적으로 사용 가능한이 구현은 유니 코드 문자를 올바르게 처리한다는 것을 이해하고 있습니다.


답변

Greg Beech unsafe는 실제로 가능한 한 빠른 옵션을 게시했습니다 (현재 위치 반전). 그러나 그의 답변에서 지적 했듯이 그것은 완전히 비참한 생각 입니다.

Array.Reverse, 가장 빠른 방법 인 합의가 너무 놀랐습니다 . 작은 문자열에 대한 방법 보다unsafe 문자열의 역 복사본을 반환 하는 방법 이 있습니다 (현재 위치 반전 shenanigans 없음) .Array.Reverse

public static unsafe string Reverse(string text)
{
    int len = text.Length;

    // Why allocate a char[] array on the heap when you won't use it
    // outside of this method? Use the stack.
    char* reversed = stackalloc char[len];

    // Avoid bounds-checking performance penalties.
    fixed (char* str = text)
    {
        int i = 0;
        int j = i + len - 1;
        while (i < len)
        {
            reversed[i++] = str[j--];
        }
    }

    // Need to use this overload for the System.String constructor
    // as providing just the char* pointer could result in garbage
    // at the end of the string (no guarantee of null terminator).
    return new string(reversed, 0, len);
}

벤치 마크 결과는 다음과 같습니다 .

Array.Reverse문자열이 커짐 에 따라 성능 향상이 줄어들었다가 메서드 에 대해 사라지는 것을 볼 수 있습니다 . 그러나 중소형 스트링의 경우이 방법을 능가하기가 어렵습니다.