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


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및 요인 에 의존하지 않는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.