문자열을 바꾸는 가장 좋은 방법


440

방금 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;
}

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


51
적절한 국제 지원을 원한다면 놀랍도록 까다 롭습니다. 예 : 크로아티아어 / 세르비아어에는 두 문자 lj, nj 등이 있습니다. "ljudi"의 올바른 역은 "idujl"이 아니라 "idulj"입니다. 아랍어, 태국어에 관해서는 훨씬 더 나빠질 것입니다.
— dbkk

임시 배열을 초기화하고 결과를 저장 한 다음 문자열로 변환하는 대신 문자열을 연결하는 것이 더 느린 지 궁금합니다.
— 머핀 남자


5
이 질문은 "최고"의 의미를 정의함으로써 개선 될 수 있습니다. 가장 빠른? 가장 읽기 쉬운가? 다양한 엣지 사례 (널 체크, 다국어 등)에서 가장 신뢰할 수 있는가? C # 및 .NET 버전에서 가장 유지 관리가 용이합니까?
— hypehuman

답변:


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

16
sambo99 : 유니 코드를 언급 할 필요는 없습니다. C #의 문자는 바이트가 아닌 유니 코드 문자입니다. Xor는 더 빠를 수 있지만 읽기가 훨씬 쉽지 않고 Array.Reverse ()가 내부적으로 사용하는 것일 수도 있습니다.
— Nick Johnson

27
@Arachnid : 실제로 C #의 문자는 UTF-16 코드 단위입니다. 보조 캐릭터를 나타내는 데는 두 가지가 필요합니다. jaggersoft.com/csharp_standard/9.4.1.htm을 참조하십시오 .
— Bradley Grainger

4
예 sambo99 나는 당신이 맞다고 생각하지만 UTF-32를 사용하는 것은 매우 드문 경우입니다. 그리고 XOR은 매우 작은 범위의 값에 대해서만 더 빠르며, 정답은 내가 생각하는 다른 길이에 대해 다른 방법을 구현하는 것입니다. 그러나 이것은 명확하고 간결하며 제 의견으로는 유익합니다.
— PeteT

21
유니 코드 제어 문자는 라틴 문자가 아닌 문자 세트에이 메소드를 쓸모 없게 만듭니다. 양말 퍼펫을 사용하여 Jon Skeet 설명을 참조하십시오 : codeblog.jonskeet.uk/2009/11/02/… (1/4 down down) 또는 비디오 : vimeo.com/7516539
— Callum Rogers

20
대리모 또는 결합 문자가 발생하지 않기를 바랍니다.
— dalle

183

여기에서 문자열을 "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를 사용 하지만,보기에서 약간 "숨겨진"것 같습니다.


10
아주 소수의 정답 하나 하나, 그리고 많은 IMO 다른 사람들보다 더 우아하고 미래의 증거
— sehe

그러나 일부 로케일에 따라 달라집니다.
— R. Martinho Fernandes

7
대부분의 다른 응답자가 틀린 접근 방식에서 MS를 제거하려고 시도하는 것은 재미 있습니다. 어떻게 대표.
— G. Stoynev

2
실제로 StringInfo를 인스턴스화하고 SubstringByTextElements (x, 1)를 반복하고 StringBuilder를 사용하여 새 문자열을 작성하는 것이 실제로 훨씬 빠릅니다.

2
Jon Skeet의 예제를 사용하여 몇 년 전에 codeblog.jonskeet.uk/2009/11/02 / ... Les Misérables 를 준 것은 약간 이상합니다. Jon은 해결책을 언급하지 않았지만 문제를 나열했습니다. 해결책을 찾았습니다. Jon skeet가 타임머신을 발명하고 2009 년으로 돌아와서 솔루션에서 사용한 문제 예를 게시했을 수 있습니다.
— barlop

126

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

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가 더 빠를 수 있습니다.


2
즉 문자열을 반환하지 않습니다 - 당신이 호출이 포장 할 필요가 "새로운 String (...)"
— 그렉 비치

BTW .. 방금 Array.Reverse의 구현을 살펴 보았고 chars에 대해 본질적으로 수행되었습니다 ... StringBuilder 옵션보다 훨씬 빠릅니다.
— Sam Saffron

그레그가 삼보를 멈출 수있게 해준 당신에게 얼마나 기쁜가?
— DOK

@ dok1-그것을 언급하지 마십시오 :) @ sambo99-이제 나는 흥미 롭습니다. 내일 코드 프로파일 러를 꺼내서 봐야합니다!
— Greg Beech

9
이 메소드는 Base Multilingual Plane 외부의 문자 (예 : 두 개의 C # 문자로 표시되는 유니 코드 문자> = U + 10000)를 포함하는 문자열을 처리하지 않습니다. 그런 문자열을 올바르게 처리하는 답변을 게시했습니다.
— Bradley Grainger

52

하나의 라이너를 따르는 것보다 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 배 느립니다.
  • 이 코드는 다른 많은 옵션과 마찬가지로 모든 종류의 다중 문자 조합을 완전히 무시하므로 이러한 문자를 포함 하지 않는 숙제 및 문자열로 사용을 제한하십시오 . 이러한 조합을 올바르게 처리하는 구현에 대해서는이 질문의 다른 답변 을 참조하십시오 .

그것은 가장 많이 올려 진 버전보다 약 5.7 배 느리므로 이것을 사용하지 않는 것이 좋습니다!
— Martin Niederl

2
가장 빠른 솔루션은 아니지만 단일 라이너로 유용합니다.
— adrianmp

49

문자열에 유니 코드 데이터 (엄격하게 말하면 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);
}

29
실제로 C #의 문자는 16 비트 UTF-16 코드 단위입니다. 보충 문자는 두 문자를 사용하여 인코딩되므로 이것이 필요합니다.
— Bradley Grainger

14
System.String은 실제로 유니 코드 보충 문자가 포함 된 문자열에 대해 HereBeDragons 속성을 노출 해야하는 것처럼 보입니다.
— Robert Rossney

4
@SebastianNegraszus : 맞습니다 :이 방법은 문자열의 코드 포인트를 반대로합니다. 반전 그래 핀 클러스터 것은 아마 전체 "유용"(하지만 처음에 임의의 문자열을 반전의 "사용"입니까?), 그러나으로 구현하기 쉽지 않은 것이 바로 내장 .NET Framework의 방법.
— Bradley Grainger

2
@Richard : grapheme 클러스터를 깨는 규칙은 결합 코드 포인트를 탐지하는 것보다 조금 더 복잡합니다. 자세한 내용은 UAX # 29의 Grapheme 클러스터 경계 에 대한 설명서 를 참조하십시오.
— Bradley Grainger

1
아주 좋은 정보! 않는 사람이 Array.Reverse 시험 실패한 검사를? 그리고 테스트는 전체 단위 테스트가 아닌 샘플 문자열을 의미합니다.이 문제에 대해 다른 사람들을 설득하는 데 정말 도움이 될 것입니다.
— Andrei Rînea

25

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

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

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


11
이것은 대리 만 제대로 처리합니다. ideone.com/yikdqX 마크 조합을 망칩니다 .
— R. Martinho Fernandes

17

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


2
큰 문자열의 StackOverflow.
— Raz Megrelidze

@rezomegreldize : 그렇습니다, 일어날 것입니다;)
— Dan Tao

15

쉽고 좋은 대답은 확장 방법을 사용하는 것입니다.

static class ExtentionMethodCollection
{
    public static string Inverse(this string @base)
    {
        return new string(@base.Reverse().ToArray());
    }
}

출력은 다음과 같습니다.

string Answer = "12345".Inverse(); // = "54321"

Reverse()및 ToArray()코드 샘플에서 잘못된 순서에 있습니다.
— Chris Walsh

@의 목적은 무엇입니까?
— user5389726598465

2
@ user5389726598465 다음 링크를 참조하십시오. docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 'base'는 C #의 키워드이므로 C # 컴파일러 에서이 코드를 식별자.
— Dyndrilliac

14

정말 위험한 게임을하고 싶다면이 방법이 Array.Reverse방법 보다 약 4 배 빠릅니다 . 포인터를 사용하여 그 반대입니다.

필자는 어떤 용도로도 이것을 권장하지 않지만 ( 이 방법을 사용하지 않아야하는 몇 가지 이유를 여기에서 살펴보십시오 ), 수행 할 수 있고 문자열이 실제로 변경되지 않는다는 것이 흥미 롭습니다. 안전하지 않은 코드를 켜면

public static unsafe string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    fixed (char* pText = text)
    {
        char* pStart = pText;
        char* pEnd = pText + text.Length - 1;
        for (int i = text.Length / 2; i >= 0; i--)
        {
            char temp = *pStart;
            *pStart++ = *pEnd;
            *pEnd-- = temp;
        }

        return text;
    }
}

나는 이것이 utf16 문자열에 대해 잘못된 결과를 반환 할 것이라고 확신합니다. 실제로 문제가 있습니다 :)
— Sam Saffron

안녕하세요 ,이 문제에 대해 실제로 요청하기 전에이 stackoverflow.com/questions/229346/… 에서이 게시물에 연결해야합니다 .
— Sam Saffron

이것은 완전히 사악하고 (당신 자신이 양보로) 경솔한 수 있지만 문자열을 사용하여 반전 할 수있는 고성능 방법은 아직 거기에 unsafe코드 없는 악 여전히 뛰는 Array.Reverse대부분의 경우는. 내 대답을 살펴보십시오.
— Dan Tao

13

wikipedia 항목을 보십시오 . 그들은 String.Reverse 확장 메소드를 구현합니다. 이를 통해 다음과 같은 코드를 작성할 수 있습니다.

string s = "olleh";
s.Reverse();

또한이 질문에 대한 다른 답변에서 제안한 ToCharArray / Reverse 조합을 사용합니다. 소스 코드는 다음과 같습니다.

public static string Reverse(this string input)
{
    char[] chars = input.ToCharArray();
    Array.Reverse(chars);
    return new String(chars);
}

확장 방법이 c # 2.0에 도입되지 않았다는 점을 제외하면 훌륭합니다.
— Kobi

11

먼저 ToCharArray문자열을 이미 char 배열로 인덱싱 할 수 있으므로 호출 할 필요가 없으므로 할당이 절약됩니다.

다음 최적화는 a를 사용하여 StringBuilder불필요한 할당을 방지 하는 것입니다 (문자열을 변경할 수 없으므로 연결하면 매번 문자열의 사본을 만듭니다). 이를 더욱 최적화하기 위해 길이를 미리 설정하여 StringBuilder버퍼를 확장 할 필요가 없습니다.

public string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    StringBuilder builder = new StringBuilder(text.Length);
    for (int i = text.Length - 1; i >= 0; i--)
    {
        builder.Append(text[i]);
    }

    return builder.ToString();
}

편집 : 성능 데이터

이 함수와 함수 Array.Reverse는 다음과 같은 간단한 프로그램을 사용하여 테스트했습니다 . 여기서 Reverse1하나는 다른 함수 Reverse2입니다.

static void Main(string[] args)
{
    var text = "abcdefghijklmnopqrstuvwxyz";

    // pre-jit
    text = Reverse1(text); 
    text = Reverse2(text);

    // test
    var timer1 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse1(text);
    }

    timer1.Stop();
    Console.WriteLine("First: {0}", timer1.ElapsedMilliseconds);

    var timer2 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse2(text);
    }

    timer2.Stop();
    Console.WriteLine("Second: {0}", timer2.ElapsedMilliseconds);

    Console.ReadLine();
}

짧은 문자열의 경우 Array.Reverse방법이 위 의 방법보다 약 두 배 빠르며, 긴 문자열의 경우 차이가 훨씬 더 두드러집니다. 따라서 Array.Reverse방법이 더 간단하고 빠르 므로이 방법을 사용하는 것이 좋습니다. 나는 이것을 당신이 해야하는 방식이 아님을 보여주기 위해 여기에 남겨 둡니다 (놀랍습니다!)


텍스트를 저장하지 않을 것입니다. 변수에서 길이는 객체를 통해 이것을 참조 할 때 약간 더 빠릅니다.
— David Robbins

10

Array.Reverse를 사용해보십시오.


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

이것은 엄청나게 빠릅니다.
— Michael Stum

왜 다운 투표? 그것을 주장하지는 않지만 오히려 내 실수에서 배우고 싶습니다.
— Mike Two

여러 가지 중에서 코드 포인트 결합을 처리하지 못합니다.
— Mooing Duck

@MooingDuck-설명해 주셔서 감사하지만 코드 포인트의 의미를 모르겠습니다. 또한 "다른 많은 것들"에 대해 자세히 설명해 주시겠습니까?
— Mike Two

@MooingDuck 코드 포인트를 찾았습니다. 예. 당신이 올바른지. 코드 포인트를 처리하지 않습니다. 그러한 단순한 질문에 대한 모든 요구 사항을 결정하는 것은 어렵습니다. 의견을 보내 주셔서 감사합니다
— Mike Two

10
public static string Reverse(string input)
{
    return string.Concat(Enumerable.Reverse(input));
}

물론 Reverse 메소드로 문자열 클래스를 확장 할 수 있습니다

public static class StringExtensions
{
    public static string Reverse(this string input)
    {
        return string.Concat(Enumerable.Reverse(input));
    }
}

Enumerable.Reverse(input)동일input.Reverse()
— 푸보

8

"최상의"는 많은 것에 의존 할 수 있지만, 여기에는 빠른 것에서 느린 것까지 몇 가지 짧은 대안이 있습니다 :

string s = "z̽a̎l͘g̈o̓😀😆", pattern = @"(?s).(?<=(?:.(?=.*$(?<=((\P{M}\p{C}?\p{M}*)\1?))))*)";

string s1 = string.Concat(s.Reverse());                          // "☐😀☐̓ög͘l̎a̽z"  👎

string s2 = Microsoft.VisualBasic.Strings.StrReverse(s);         // "😆😀o̓g̈l͘a̎̽z"  👌

string s3 = string.Concat(StringInfo.ParseCombiningCharacters(s).Reverse()
    .Select(i => StringInfo.GetNextTextElement(s, i)));          // "😆😀o̓g̈l͘a̎z̽"  👍

string s4 = Regex.Replace(s, pattern, "$2").Remove(s.Length);    // "😆😀o̓g̈l͘a̎z̽"  👍

8

.NET Core 2.1부터는 string.Create메소드를 사용하여 문자열을 되 돌리는 새로운 방법이 있습니다.

"Les Mise \ u0301rables"가 "selbarésiM seL"로 변환되므로이 솔루션은 유니 코드 결합 문자 등을 올바르게 처리하지 않습니다. 다른 답변 보다 나은 솔루션.

public static string Reverse(string input)
{
    return string.Create<string>(input.Length, input, (chars, state) =>
    {
        state.AsSpan().CopyTo(chars);
        chars.Reverse();
    });
}

이것은 본질적으로의 input문자열을 새로운 문자열로 복사하고 그 자리 에서 새로운 문자열을 뒤집습니다.

왜 string.Create유용한가요?

기존 배열에서 문자열을 만들면 새로운 내부 배열이 할당되고 값이 복사됩니다. 그렇지 않으면 (안전한 환경에서) 생성 후 문자열을 변경할 수 있습니다. 즉, 다음 코드 조각에서 하나는 버퍼로, 하나는 문자열의 내부 배열로, 길이 10의 배열을 두 번 할당해야합니다.

var chars = new char[10];
// set array values
var str = new string(chars);

string.Create본질적으로 문자열 생성 시간 동안 내부 배열을 조작 할 수 있습니다. 이것은 더 이상 버퍼가 필요하지 않으므로 하나의 문자 배열을 할당하는 것을 피할 수 있습니다.

스티브 고든 자세한 내용에 대해 작성했습니다 여기에 . MSDN에 대한 기사도 있습니다 .

사용하는 방법 string.Create?

public static string Create<TState>(int length, TState state, SpanAction<char, TState> action);

이 방법에는 세 가지 매개 변수가 사용됩니다.

  1. 만들 문자열의 길이
  2. 새 문자열을 동적으로 생성하는 데 사용하려는 데이터
  3. 데이터에서 최종 문자열을 만드는 델리게이트. 여기서 첫 번째 매개 변수 char는 새 문자열 의 내부 배열을 가리키고 두 번째는 전달한 데이터 (상태) string.Create입니다.

델리게이트 내에서 데이터에서 새 문자열을 만드는 방법을 지정할 수 있습니다. 이 경우 입력 문자열의 문자를 Span새 문자열 에서 사용 된 문자로 복사하면 됩니다. 그런 다음 우리는Span 전체 문자열을 뒤집습니다.

벤치 마크

문자열을 되 돌리는 제안 된 방법과 허용 된 답변을 비교하기 위해 BenchmarkDotNet을 사용하여 두 가지 벤치 마크를 작성했습니다.

public class StringExtensions
{
    public static string ReverseWithArray(string input)
    {
        var charArray = input.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }

    public static string ReverseWithStringCreate(string input)
    {
        return string.Create(input.Length, input, (chars, state) =>
        {
            state.AsSpan().CopyTo(chars);
            chars.Reverse();
        });
    }
}

[MemoryDiagnoser]
public class StringReverseBenchmarks
{
    private string input;

    [Params(10, 100, 1000)]
    public int InputLength { get; set; }


    [GlobalSetup]
    public void SetInput()
    {
        // Creates a random string of the given length
        this.input = RandomStringGenerator.GetString(InputLength);
    }

    [Benchmark(Baseline = true)]
    public string WithReverseArray() => StringExtensions.ReverseWithArray(input);

    [Benchmark]
    public string WithStringCreate() => StringExtensions.ReverseWithStringCreate(input);
}

내 컴퓨터의 결과는 다음과 같습니다.

| Method           | InputLength |         Mean |      Error |    StdDev |  Gen 0 | Allocated |
| ---------------- | ----------- | -----------: | ---------: | --------: | -----: | --------: |
| WithReverseArray | 10          |    45.464 ns |  0.4836 ns | 0.4524 ns | 0.0610 |      96 B |
| WithStringCreate | 10          |    39.749 ns |  0.3206 ns | 0.2842 ns | 0.0305 |      48 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 100         |   175.162 ns |  2.8766 ns | 2.2458 ns | 0.2897 |     456 B |
| WithStringCreate | 100         |   125.284 ns |  2.4657 ns | 2.0590 ns | 0.1473 |     232 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 1000        | 1,523.544 ns |  9.8808 ns | 8.7591 ns | 2.5768 |    4056 B |
| WithStringCreate | 1000        | 1,078.957 ns | 10.2948 ns | 9.6298 ns | 1.2894 |    2032 B |

보시다시피 ReverseWithStringCreate, ReverseWithArray메소드에서 사용하는 메모리의 절반 만 할당합니다 .


Linq 역보다 훨씬 빠릅니다
— code4j

7

함수를 신경 쓰지 말고 그냥 그 자리에 두십시오. 참고 : 두 번째 줄은 일부 VS 버전의 직접 실행 창에서 인수 예외를 발생시킵니다.

string s = "Blah";
s = new string(s.ToCharArray().Reverse().ToArray()); 

1
어떤 사람은 이유를 설명하지 않고 모든 답변 (광산 포함)을 다운 보트하는 데 시간이 걸렸습니다.
— Marcel Valdez Orozco

당신이 생성되기 때문에이 자리에 정말 아니다new string
— mbadawi23

5

긴 글은 죄송하지만 재미있을 것입니다

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

namespace ConsoleApplication1
{
    class Program
    {
        public static string ReverseUsingArrayClass(string text)
        {
            char[] chars = text.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }

        public static string ReverseUsingCharacterBuffer(string text)
        {
            char[] charArray = new char[text.Length];
            int inputStrLength = text.Length - 1;
            for (int idx = 0; idx <= inputStrLength; idx++) 
            {
                charArray[idx] = text[inputStrLength - idx];                
            }
            return new string(charArray);
        }

        public static string ReverseUsingStringBuilder(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return text;
            }

            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }

            return builder.ToString();
        }

        private static string ReverseUsingStack(string input)
        {
            Stack<char> resultStack = new Stack<char>();
            foreach (char c in input)
            {
                resultStack.Push(c);
            }

            StringBuilder sb = new StringBuilder();
            while (resultStack.Count > 0)
            {
                sb.Append(resultStack.Pop());
            }
            return sb.ToString();
        }

        public static string ReverseUsingXOR(string text)
        {
            char[] charArray = text.ToCharArray();
            int length = text.Length - 1;
            for (int i = 0; i < length; i++, length--)
            {
                charArray[i] ^= charArray[length];
                charArray[length] ^= charArray[i];
                charArray[i] ^= charArray[length];
            }

            return new string(charArray);
        }


        static void Main(string[] args)
        {
            string testString = string.Join(";", new string[] {
                new string('a', 100), 
                new string('b', 101), 
                new string('c', 102), 
                new string('d', 103),                                                                   
            });
            int cycleCount = 100000;

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingCharacterBuffer(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingCharacterBuffer: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingArrayClass(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingArrayClass: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStringBuilder(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStringBuilder: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStack(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStack: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingXOR(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingXOR: " + stopwatch.ElapsedMilliseconds + "ms");            
        }
    }
}

결과 :

  • ReverseUsingCharacterBuffer : 346ms
  • ReverseUsingArrayClass : 87ms
  • ReverseUsingStringBuilder : 824ms
  • 역 사용 스택 : 2086ms
  • 반전 사용 XOR : 319ms

내 게시물에 커뮤니티 위키와 비슷한 비교를 추가 했으므로 편집 할 수 있어야합니다. 성능은 실제로 문자열의 길이와 알고리즘에 따라 다르므로 그래프로 그리는 것이 흥미로울 것입니다. 나는 여전히 Array.Reverse가 모든 경우에 가장 빠를 것이라고 생각합니다 ...
— Sam Saffron

마법의 TrySZReverse 함수 (Reverse 구현에 사용됨)가 실패하면 "모든 경우에 가장 빠를 것입니다", Array.Reverse는 권투와 관련된 간단한 구현으로 대체되므로 내 방법이 이길 것입니다. 그러나 TrySZReverse를 실패하게 만드는 조건이 무엇인지 모르겠습니다.
— aku

모든 경우에 가장 빠르지는 않습니다. :), 내 게시물을 업데이트했습니다. 정확성과 속도 모두 유니 코드로 테스트해야합니다.
— Sam Saffron

5
public string Reverse(string input)
{
    char[] output = new char[input.Length];

    int forwards = 0;
    int backwards = input.Length - 1;

    do
    {
        output[forwards] = input[backwards];
        output[backwards] = input[forwards];
    }while(++forwards <= --backwards);

    return new String(output);
}

public string DotNetReverse(string input)
{
    char[] toReverse = input.ToCharArray();
    Array.Reverse(toReverse);
    return new String(toReverse);
}

public string NaiveReverse(string input)
{
    char[] outputArray = new char[input.Length];
    for (int i = 0; i < input.Length; i++)
    {
        outputArray[i] = input[input.Length - 1 - i];
    }

    return new String(outputArray);
}    

public string RecursiveReverse(string input)
{
    return RecursiveReverseHelper(input, 0, input.Length - 1);
}

public string RecursiveReverseHelper(string input, int startIndex , int endIndex)
{
    if (startIndex == endIndex)
    {
        return "" + input[startIndex];
    }

    if (endIndex - startIndex == 1)
    {
        return "" + input[endIndex] + input[startIndex];
    }

    return input[endIndex] + RecursiveReverseHelper(input, startIndex + 1, endIndex - 1) + input[startIndex];
}


void Main()
{
    int[] sizes = new int[] { 10, 100, 1000, 10000 };
    for(int sizeIndex = 0; sizeIndex < sizes.Length; sizeIndex++)
    {
        string holaMundo  = "";
        for(int i = 0; i < sizes[sizeIndex]; i+= 5)
        {   
            holaMundo += "ABCDE";
        }

        string.Format("\n**** For size: {0} ****\n", sizes[sizeIndex]).Dump();

        string odnuMaloh = DotNetReverse(holaMundo);

        var stopWatch = Stopwatch.StartNew();
        string result = NaiveReverse(holaMundo);
        ("Naive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = Reverse(holaMundo);
        ("Efficient linear Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = RecursiveReverse(holaMundo);
        ("Recursive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = DotNetReverse(holaMundo);
        ("DotNet Reverse Ticks: " + stopWatch.ElapsedTicks).Dump();
    }
}

산출

크기 : 10

Naive Ticks: 1
Efficient linear Ticks: 0
Recursive Ticks: 2
DotNet Reverse Ticks: 1

사이즈 : 100

Naive Ticks: 2
Efficient linear Ticks: 1
Recursive Ticks: 12
DotNet Reverse Ticks: 1

크기 : 1000

Naive Ticks: 5
Efficient linear Ticks: 2
Recursive Ticks: 358
DotNet Reverse Ticks: 9

크기 : 10000

Naive Ticks: 32
Efficient linear Ticks: 28
Recursive Ticks: 84808
DotNet Reverse Ticks: 33

1
에서 빈 문자열을 확인해야합니다 Reverse(...). 그렇지 않으면 좋은 일입니다.
— 라라


4

스택 기반 솔루션.

    public static string Reverse(string text)
    {
        var stack = new Stack<char>(text);
        var array = new char[stack.Count];

        int i = 0;
        while (stack.Count != 0)
        {
            array[i++] = stack.Pop();
        }

        return new string(array);
    }

또는

    public static string Reverse(string text)
    {
        var stack = new Stack<char>(text);
        return string.Join("", stack);
    }

4

재귀 예제를 제출해야했습니다.

private static string Reverse(string str)
{
    if (str.IsNullOrEmpty(str) || str.Length == 1)
        return str;
    else
        return str[str.Length - 1] + Reverse(str.Substring(0, str.Length - 1));
}

1
길이가 0 인 문자열은 처리되지 않습니다.
— bohdan_trotsenko

이것은 유용하지 않습니다.
— user3613932

3

어때요?

    private string Reverse(string stringToReverse)
    {
        char[] rev = stringToReverse.Reverse().ToArray();
        return new string(rev); 
    }

위의 다른 방법과 동일한 코드 포인트 문제가 있으며 ToCharArray처음 수행 할 때보 다 훨씬 느리게 수행됩니다 . LINQ 열거자는 또한보다 느립니다 Array.Reverse().
— Abel

3

Microsoft.VisualBasic.Strings 에서 C # 포트를 만들었습니다 . 왜 그들이 VB의 유용한 함수를 Framework의 System.String 외부에 유지하지만 여전히 Microsoft.VisualBasic 아래에 유지하는지 잘 모르겠습니다. 재무 기능에 대한 동일한 시나리오 (예 :) Microsoft.VisualBasic.Financial.Pmt().

public static string StrReverse(this string expression)
{
    if ((expression == null))
        return "";

    int srcIndex;

    var length = expression.Length;
    if (length == 0)
        return "";

    //CONSIDER: Get System.String to add a surrogate aware Reverse method

    //Detect if there are any graphemes that need special handling
    for (srcIndex = 0; srcIndex <= length - 1; srcIndex++)
    {
        var ch = expression[srcIndex];
        var uc = char.GetUnicodeCategory(ch);
        if (uc == UnicodeCategory.Surrogate || uc == UnicodeCategory.NonSpacingMark || uc == UnicodeCategory.SpacingCombiningMark || uc == UnicodeCategory.EnclosingMark)
        {
            //Need to use special handling
            return InternalStrReverse(expression, srcIndex, length);
        }
    }

    var chars = expression.ToCharArray();
    Array.Reverse(chars);
    return new string(chars);
}

///<remarks>This routine handles reversing Strings containing graphemes
/// GRAPHEME: a text element that is displayed as a single character</remarks>
private static string InternalStrReverse(string expression, int srcIndex, int length)
{
    //This code can only be hit one time
    var sb = new StringBuilder(length) { Length = length };

    var textEnum = StringInfo.GetTextElementEnumerator(expression, srcIndex);

    //Init enumerator position
    if (!textEnum.MoveNext())
    {
        return "";
    }

    var lastSrcIndex = 0;
    var destIndex = length - 1;

    //Copy up the first surrogate found
    while (lastSrcIndex < srcIndex)
    {
        sb[destIndex] = expression[lastSrcIndex];
        destIndex -= 1;
        lastSrcIndex += 1;
    }

    //Now iterate through the text elements and copy them to the reversed string
    var nextSrcIndex = textEnum.ElementIndex;

    while (destIndex >= 0)
    {
        srcIndex = nextSrcIndex;

        //Move to next element
        nextSrcIndex = (textEnum.MoveNext()) ? textEnum.ElementIndex : length;
        lastSrcIndex = nextSrcIndex - 1;

        while (lastSrcIndex >= srcIndex)
        {
            sb[destIndex] = expression[lastSrcIndex];
            destIndex -= 1;
            lastSrcIndex -= 1;
        }
    }

    return sb.ToString();
}

+1, 멋진 추가 기능! 방금 BMP에 3 개의 결합 분음 부호와 아스트랄 평면 (비 BMP)에서 하나의 결합 표시 (MUSICAL SYMBOL COMBINING STEM) string s = "abo\u0327\u0307\u035d\U0001d166cd"가 포함 된 문자가 포함 된으로 시도했지만 o그대로 유지합니다. 그러나 전체 문자열에 대해 두 번 가야하기 때문에 이러한 문자열이 긴 문자열의 끝에 만 나타나는 경우이 방법이 느립니다.
— Abel

3

이 오래된 스레드에 게시하여 죄송합니다. 인터뷰를 위해 몇 가지 코드를 연습하고 있습니다.

이것이 C #을 위해 생각해 낸 것입니다. 리팩토링 이전의 첫 번째 버전은 끔찍했습니다.

static String Reverse2(string str)
{
    int strLen = str.Length, elem = strLen - 1;
    char[] charA = new char[strLen];

    for (int i = 0; i < strLen; i++)
    {
        charA[elem] = str[i];
        elem--;
    }

    return new String(charA);
}

받는 반면에 Array.Reverse아래의 방법, 더 빨리 문자열의 12 자 이하로 나타납니다. 13 자 이후에는 Array.Reverse시작이 빨라지고 결국에는 속도가 상당히 높아집니다. 나는 단지 속도가 어디에서 변화하기 시작하는지 지적하고 싶었다.

static String Reverse(string str)
{     
    char[] charA = str.ToCharArray();

    Array.Reverse(charA);

    return new String(charA);
}

문자열의 100 자에서 버전 x 4보다 빠릅니다. 그러나 문자열이 항상 13 자 미만이라는 것을 알고 있다면 내가 만든 문자를 사용합니다.

테스트는 Stopwatch5000000 회 반복되었습니다. 또한 내 버전에서 대리 문자를 처리하거나 문자 상황을 Unicode인코딩으로 처리하는지 확실하지 않습니다 .


2

"더 나은 방법"은 상황, 성능, 우아함, 유지 보수성 등에서 당신에게 더 중요한 것에 달려 있습니다.

어쨌든, Array.Reverse를 사용하는 접근법이 있습니다.

string inputString="The quick brown fox jumps over the lazy dog.";
char[] charArray = inputString.ToCharArray(); 
Array.Reverse(charArray); 

string reversed = new string(charArray);

2

인터뷰에서 나왔는데 Array.Reverse를 사용할 수 없다는 말을 들었다면 이것이 가장 빠를 것이라고 생각합니다. 새 문자열을 만들지 않고 배열의 절반 이상 만 반복합니다 (예 : O (n / 2) 반복)

    public static string ReverseString(string stringToReverse)
    {
        char[] charArray = stringToReverse.ToCharArray();
        int len = charArray.Length-1;
        int mid = len / 2;

        for (int i = 0; i < mid; i++)
        {
            char tmp = charArray[i];
            charArray[i] = charArray[len - i];
            charArray[len - i] = tmp;
        }
        return new string(charArray);
    }

2
꽤 확실한 stringToReverse.ToCharArray () 호출은 O (N) 실행 시간을 생성합니다.
— Marcel Valdez Orozco

에서 큰-O 표기법 ,하지에 의존 요인 x, 또는 귀하의 경우는 n, 사용되지 않습니다. 알고리즘에는 성능이 f(x) = x + ½x + C있으며 여기서 C는 일정합니다. 모두 이후 C및 요인 1½에 의존하지 않는x 알고리즘은 O(x)입니다. 그렇다고 length 입력이 더 빠르지는 x않지만 성능은 입력 길이에 선형 적으로 의존합니다. @MarcelValdezOrozco에 대답 O(n)하기 위해 16 바이트 청크로 복사하지만 속도를 향상시킵니다 ( memcpy총 길이에 직선 을 사용하지 않음 ).
— Abel

2

ASCII 문자 만 포함하는 문자열이 있으면이 방법을 사용할 수 있습니다.

    public static string ASCIIReverse(string s)
    {
        byte[] reversed = new byte[s.Length];

        int k = 0;
        for (int i = s.Length - 1; i >= 0; i--)
        {
            reversed[k++] = (byte)s[i];
        }

        return Encoding.ASCII.GetString(reversed);
    }

2

먼저 이해해야 할 것은 str + =가 문자열 메모리의 크기를 조정하여 1 개의 추가 문자를위한 공간을 확보한다는 것입니다. 괜찮지 만, 예를 들어, 1000 페이지를 넘기고 싶은 책을 가지고 있다면, 실행하는데 시간이 오래 걸릴 것입니다.

일부 사람들이 제안 할 수있는 솔루션은 StringBuilder를 사용하는 것입니다. + =를 수행 할 때 문자열 빌더는 문자를 추가 할 때마다 재 할당 할 필요가 없도록 새 문자를 보유하기 위해 훨씬 더 많은 메모리 청크를 할당합니다.

빠르고 최소한의 솔루션을 원한다면 다음을 제안합니다.

            char[] chars = new char[str.Length];
            for (int i = str.Length - 1, j = 0; i >= 0; --i, ++j)
            {
                chars[j] = str[i];
            }
            str = new String(chars);

이 솔루션에는 char []가 초기화 될 때 하나의 초기 메모리 할당이 있고 문자열 생성자가 char 배열에서 문자열을 빌드 할 때 하나의 할당이 있습니다.

내 시스템에서 2 750 000 자의 문자열을 반대로하는 테스트를 실행했습니다. 10 회 실행 결과는 다음과 같습니다.

StringBuilder : 190K-200K 틱

문자 배열 : 130K-160K 틱

또한 일반 String + =에 대한 테스트를 실행했지만 출력없이 10 분 후에 포기했습니다.

그러나 작은 문자열의 경우 StringBuilder가 더 빠르므로 입력을 기반으로 구현을 결정해야합니다.

건배


작동하지 않습니다😀Les Misérables
— Charles Charles

@Charles 아 그래, 내가 생각하는 문자 세트 제한이 있습니다.
— Reasurria

2
public static string reverse(string s) 
{
    string r = "";
    for (int i = s.Length; i > 0; i--) r += s[i - 1];
    return r;
}

1
public static string Reverse2(string x)
        {
            char[] charArray = new char[x.Length];
            int len = x.Length - 1;
            for (int i = 0; i <= len; i++)
                charArray[i] = x[len - i];
            return new string(charArray);
        }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.