Melhor maneira de reverter uma string


440

Eu apenas tive que escrever uma função de reversão de string em C # 2.0 (ou seja, LINQ não disponível) e surgiu com isso:

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

Pessoalmente, não sou louco pela função e estou convencido de que há uma maneira melhor de fazê-la. Existe?


51
Surpreendentemente complicado, se você quiser um suporte internacional adequado. Exemplo: croata / sérvio tem letras de dois caracteres lj, nj etc. O reverso adequado de "ljudi" é "idulj", NÃO "idujl". Eu tenho certeza que você se saem muito pior quando se trata de árabe, tailandês etc.
dbkk

Gostaria de saber se é mais lento concat uma string em vez de inicializar uma matriz temporária e armazenar os resultados nela e, finalmente, convertê-la em uma string?
The Muffin Man

2
Tópico relacionado muito mais recente: reverter uma string com caracteres de destaque?
Jeppe Stig Nielsen

5
Essa pergunta pode ser melhorada definindo o que você quer dizer com "melhor". O mais rápido? Mais legível? Mais confiável em vários casos extremos (verificações nulas, vários idiomas etc.)? Mais manutenível em versões do C # e .NET?
Hypehuman

Respostas:


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

16
sambo99: Não é necessário mencionar unicode: caracteres em C # são caracteres unicode, não bytes. O Xor pode ser mais rápido, mas, além de muito menos legível, pode ser o que Array.Reverse () usa internamente.
Nick Johnson

27
@Arachnid: Na verdade, os caracteres em C # são unidades de código UTF-16; são necessários dois deles para representar um caráter suplementar. Consulte jaggersoft.com/csharp_standard/9.4.1.htm .
Bradley Grainger

4
Sim sambo99 Suponho que você esteja correto, mas é um caso muito raro usar o UTF-32. E o XOR é apenas mais rápido para uma faixa muito pequena de valores, a resposta correta seria implementar métodos diferentes para comprimentos diferentes, suponho. Mas isso é claro e conciso, o que é um benefício na minha opinião.
Petet

21
Os caracteres de controle Unicode tornam esse método inútil para conjuntos de caracteres não latinos. Veja Jon Skeet explicação, usando um fantoche: codeblog.jonskeet.uk/2009/11/02/... (1/4 do caminho para baixo), ou o vídeo: vimeo.com/7516539
Callum Rogers

20
Espero que você não encontre substitutos ou personagens que combinem.
dalle

183

Aqui uma solução que reverte corretamente a string "Les Mise\u0301rables"como "selbare\u0301siM seL". Isso deve render como selbarésiM seL, não selbaŕesiM seL(observe a posição do acento), como seria o resultado da maioria das implementações baseadas em unidades de código ( Array.Reverse, etc) ou mesmo em pontos de código (revertendo com cuidado especial para pares substitutos).

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

(E exemplo de execução ao vivo aqui: https://ideone.com/DqAeMJ )

Ele simplesmente usa a API .NET para iteração de cluster grafema , que existe desde sempre, mas parece um pouco "escondido" da vista.


10
+1 Uma das poucas respostas corretas e muito mais elegantes e à prova do futuro do que qualquer outra, IMO
sehe

Porém, isso falha para algumas coisas dependentes da localidade.
R. Martinho Fernandes

7
É engraçado como a maioria dos outros atendentes está tentando raspar as mensagens de outras abordagens incorretas. Quão representativo.
G. Stoynev 5/12

2
Na verdade, é significativamente mais rápido instanciar StringInfo (s), iterar por SubstringByTextElements (x, 1) e criar uma nova string com um StringBuilder.

2
É um pouco estranho o fato de você ter usado o exemplo de Jon Skeet, que ele deu anos antes codeblog.jonskeet.uk/2009/11/02/… Les Misérables (embora Jon não tenha mencionado uma solução, ele apenas listou questões). É bom que você tenha encontrado uma solução. Talvez Jon skeet tenha inventado uma máquina do tempo, retornado a 2009 e postado o exemplo de problema que você usou em sua solução.
barlop

126

Esta está se tornando uma pergunta surpreendentemente complicada.

Eu recomendaria o uso do Array.Reverse na maioria dos casos, pois é codificado nativamente e é muito simples de manter e entender.

Parece ter um desempenho superior ao StringBuilder em todos os casos que testei.

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

Existe uma segunda abordagem que pode ser mais rápida para determinados comprimentos de string que usa 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);
    }

Nota Se você deseja suportar o conjunto de caracteres Unicode UTF16 completo, leia isto . E use a implementação lá. Ele pode ser otimizado ainda mais usando um dos algoritmos acima e executando a string para limpá-lo após os caracteres serem revertidos.

Aqui está uma comparação de desempenho entre o método StringBuilder, Array.Reverse e 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();
        }
    }
}

Aqui estão os resultados:

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.

Parece que o Xor pode ser mais rápido para seqüências curtas.


2
Isso não retorna uma string - você precisa agrupar isso em uma chamada para "new String (...)"
Greg Beech

BTW .. Acabei de dar uma olhada na implementação do Array.Reverse, e ele é feito de maneira ingênua para chars ... deve ser muito mais rápido que a opção StringBuilder.
Sam Saffron

Que gentileza sua, Greg, de ajudar Sambo a chegar a uma solução melhor, em vez de fazer um voto negativo.
DOK

@ dok1 - não mencione isso :) @ sambo99 - agora estou intrigado, vou ter que escolher um perfilador de código amanhã e dar uma olhada!
Greg Beech

9
Esses métodos não manipulam seqüências de caracteres contendo caracteres fora do Plano multilíngue básico, ou seja, caracteres Unicode> = U + 10000 que são representados com dois caracteres C #. Publiquei uma resposta que lida com essas strings corretamente.
Bradley Grainger

52

Se você pode usar o LINQ (.NET Framework 3.5+), seguir um liner fornecerá um código curto. Não se esqueça de adicionar using System.Linq;para ter acesso a Enumerable.Reverse:

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

Notas:

  • não é a versão mais rápida - de acordo com Martin Niederl 5,7 vezes mais lento que a escolha mais rápida aqui.
  • esse código, assim como muitas outras opções, ignora completamente todos os tipos de combinações de vários caracteres; portanto, limite o uso a tarefas de casa e cadeias que não contenham esses caracteres. Veja outra resposta nesta pergunta para implementação que lida corretamente com essas combinações.

Isso é cerca de 5,7 vezes mais lento que a versão mais votada, então eu não recomendaria usar isso!
Martin Niederl

2
Não é a solução mais rápida, mas é útil como uma linha.
adrianmp

49

Se a sequência contiver dados Unicode (caracteres estritamente não-BMP), os outros métodos publicados a danificarão, porque você não poderá trocar a ordem das unidades de código substituto alto e baixo ao reverter a sequência. (Mais informações sobre isso podem ser encontradas no meu blog .)

O exemplo de código a seguir reverterá corretamente uma seqüência de caracteres que contém caracteres não-BMP, por exemplo, "\ U00010380 \ U00010381" (letra ugarítica Alpa, letra ugarítica 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
Na verdade, os caracteres em C # são unidades de código UTF-16 de 16 bits; um caractere suplementar é codificado usando dois deles, então isso é necessário,
Bradley Grainger

14
Parece que System.String realmente deve expor uma propriedade HereBeDragons para seqüências de caracteres que contêm caracteres suplementares Unicode.
Robert Rossney 23/10/08

4
@SebastianNegraszus: Isso está correto: esse método apenas reverte os pontos de código na string. A reversão dos clusters de grafema provavelmente seria mais "útil" no geral (mas qual é o "uso" de reverter uma cadeia arbitrária em primeiro lugar?), Mas não é fácil de implementar apenas com os métodos internos do .NET Framework.
amigos estão dizendo sobre bradley

2
@ Richard: As regras para quebrar grupos de grafemas são um pouco mais complicadas do que apenas detectar pontos de código combinados; consulte a documentação sobre limites de cluster Grapheme no UAX # 29 para obter mais informações.
Bradley Grainger

1
Muito boa informação! Será que NINGUÉM ter um teste falhando para o teste Array.Reverse? E pelo teste Quero dizer uma string amostra não todo um teste de unidade ... Seria realmente me (e outros) ajudar a convencer pessoas diferentes sobre esta questão ..
Andrei Rînea

25

Ok, no interesse de "não se repita", ofereço a seguinte solução:

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

Meu entendimento é que essa implementação, disponível por padrão no VB.NET, lida corretamente com caracteres Unicode.


11
Isso apenas lida com substitutos corretamente. Ele bagunça a combinação de marcas: ideone.com/yikdqX .
R. Martinho Fernandes

17

Greg Beech postou uma unsafeopção que é realmente o mais rápida possível (é uma reversão no local); mas, como ele indicou em sua resposta, é uma ideia completamente desastrosa .

Dito isto, estou surpreso por haver tanto consenso que Array.Reverseé o método mais rápido. Ainda existe uma unsafeabordagem que retorna uma cópia invertida de uma string (sem travessias de reversão no local) significativamente mais rápido que o Array.Reversemétodo para strings pequenas:

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

Aqui estão alguns resultados de benchmark .

Você pode ver que o ganho de desempenho diminui e desaparece no Array.Reversemétodo à medida que as seqüências de caracteres aumentam. Para cordas pequenas e médias, porém, é difícil vencer esse método.


2
StackOverflow em strings grandes.
Raz Megrelidze 28/01

@rezomegreldize: Sim, isso vai acontecer;)
Dan Tao

15

A resposta fácil e agradável é usar o método de extensão:

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

e aqui está a saída:

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

Reverse()e ToArray()estão na ordem errada no seu exemplo de código.
22417 Chris Walsh

A que finalidade o @ serve?
user5389726598465

2
@ user5389726598465 Consulte este link: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Como 'base' é uma palavra-chave em C #, deve ser prefixado com @ para que o compilador C # o interprete como um identificador.
Dyndrilliac 10/05/19

14

Se você quiser jogar um jogo realmente perigoso, essa é de longe a maneira mais rápida (cerca de quatro vezes mais rápida que o Array.Reversemétodo). É um reverso no local usando ponteiros.

Observe que eu realmente não recomendo isso para qualquer uso ( veja aqui por alguns motivos pelos quais você não deve usar esse método ), mas é interessante ver que isso pode ser feito e que as strings não são realmente imutáveis depois de ativar o código não seguro.

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

Im certeza que isso irá retornar resultados incorretos para strings UTF16, é realmente perguntando problemas :)
Sam Saffron

Oi você deve conectar-se a este post sobre este stackoverflow.com/questions/229346/... , como eu disse antes isso está realmente pedindo para ter problemas ...
Sam Saffron

Isto pode ser completamente mal e mal aconselhado (como você mesmo admitir), mas ainda há uma maneira de alto desempenho para reverter uma string usando unsafecódigo que não é o mal e ainda bate Array.Reverseem muitos casos. Dê uma olhada na minha resposta.
Dan Tao

13

Dê uma olhada na entrada da Wikipedia aqui . Eles implementam o método de extensão String.Reverse. Isso permite que você escreva um código como este:

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

Eles também usam a combinação ToCharArray / Reverse sugerida por outras respostas a esta pergunta. O código fonte fica assim:

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

Isso é maravilhoso, exceto que os métodos de extensão não foram introduzidos no c # 2.0.
244 Kobi

11

Em primeiro lugar você não precisa ligar ToCharArray pois uma string já pode ser indexada como uma matriz de caracteres, portanto, isso poupará uma alocação.

A próxima otimização é usar a StringBuilderpara evitar alocações desnecessárias (como as strings são imutáveis, a concatenação faz uma cópia da string a cada vez). Para otimizar ainda mais isso, predefinimos o tamanho do arquivo StringBuilderpara que ele não precise expandir seu buffer.

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

Editar: Dados de Desempenho

Testei essa função e a função usando Array.Reverseo seguinte programa simples, onde Reverse1é uma função e Reverse2a outra:

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

Acontece que para cordas curtas o Array.Reversemétodo é duas vezes mais rápido que o descrito acima, e para cordas mais longas a diferença é ainda mais acentuada. Portanto, dado que o Array.Reversemétodo é mais simples e mais rápido, recomendo que você o use em vez deste. Deixo este aqui em cima apenas para mostrar que não é assim que você deve fazer (para minha surpresa!)


O armazenamento de texto. O comprimento de uma variável fornece um pouco mais de velocidade à medida que você faz referência a isso por meio de um objeto?
David Robbins

10

Tente usar Array.Reverse


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

Isso é incrivelmente rápido.
Michael Stum

Por que o voto negativo? Não estou discutindo, mas prefiro aprender com meus erros.
Mike Two

Não consegue lidar com a combinação de pontos de código, entre muitas outras coisas.
Mooing Duck

@MooingDuck - obrigado por explicar, mas não sei o que você quer dizer com pontos de código. Você também pode elaborar "muitas outras coisas".
Mike Two

@MooingDuck procurei pontos de código. Sim. Você está certo. Ele não lida com pontos de código. É difícil determinar todos os requisitos para uma pergunta tão simples. Obrigado pelo feedback
Mike Two

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

Claro que você pode estender a classe de string com o método Reverse

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

Enumerable.Reverse(input)é igual ainput.Reverse()
fubo 17/05/19

8

"Melhor" pode depender de muitas coisas, mas aqui estão mais algumas alternativas curtas, ordenadas de rápida a lenta:

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

A partir do .NET Core 2.1, há uma nova maneira de reverter uma string usando o string.Createmétodo

Observe que esta solução não manipula caracteres combinados Unicode etc. corretamente, pois "Les Mise \ u0301rables" seria convertido em "selbarésiM seL". O outro responde por uma solução melhor.

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

Isso essencialmente copia os caracteres de inputpara uma nova sequência e reverte a nova sequência no local.

Por que é string.Createútil?

Quando criamos uma string a partir de uma matriz existente, uma nova matriz interna é alocada e os valores são copiados. Caso contrário, seria possível alterar uma sequência após sua criação (em um ambiente seguro). Ou seja, no snippet a seguir, temos que alocar uma matriz de comprimento 10 duas vezes, uma como buffer e outra como matriz interna da string.

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

string.Createessencialmente nos permite manipular a matriz interna durante o tempo de criação da string. Ou seja, não precisamos mais de um buffer e, portanto, podemos evitar a alocação dessa matriz de caracteres.

Steve Gordon escreveu sobre isso com mais detalhes aqui . Há também um artigo no MSDN .

Como usar string.Create?

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

O método usa três parâmetros:

  1. O comprimento da string a ser criada,
  2. os dados que você deseja usar para criar dinamicamente a nova sequência,
  3. e um delegado que cria a sequência final a partir dos dados, onde o primeiro parâmetro aponta para a charmatriz interna da nova sequência e o segundo são os dados (estado) aos quais você passou string.Create.

Dentro do delegado, podemos especificar como a nova string é criada a partir dos dados. No nosso caso, apenas copiamos os caracteres da string de entrada para os Spanusados ​​pela nova string. Então invertemos oSpan e, portanto, toda a cadeia é invertida.

Benchmarks

Para comparar minha maneira proposta de reverter uma string com a resposta aceita, escrevi dois benchmarks usando o 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);
}

Aqui estão os resultados na minha máquina:

| 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 |

Como você pode ver, ReverseWithStringCreatealocamos apenas metade da memória usada pelo ReverseWithArraymétodo.


É muito mais rápido do que o Linq inversa
code4j

7

Não se preocupe com uma função, apenas faça-a no lugar. Nota: A segunda linha lançará uma exceção de argumento na janela Imediata de algumas versões do VS.

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

1
Um cara tirou um tempo para votar cada resposta (incluindo a minha) sem explicar o porquê.
Marcel Valdez Orozco

Isto não é realmente no lugar, desde que você está criando umnew string
mbadawi23

5

Desculpe pela postagem longa, mas isso pode ser interessante

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

Resultados:

  • ReverseUsingCharacterBuffer: 346ms
  • ReverseUsingArrayClass: 87ms
  • ReverseUsingStringBuilder: 824ms
  • ReverseUsingStack: 2086ms
  • ReverseUsingXOR: 319ms

Eu adicionei uma comparação semelhante no meu post, é um wiki da comunidade para que você possa editar. O desempenho realmente depende do comprimento da string e do algoritmo, seria interessante fazer um gráfico. Eu ainda acho que Array.Reverse será o mais rápido em todos os casos ...
Sam Saffron

"será mais rápido em todos os casos" quando a função mágica TrySZReverse (usada na implementação reversa) falhar, Array.Reverse reversos para uma implementação simples envolvendo boxe, para que meu método seja vencedor. No entanto, não sei qual é uma condição para fazer com que o TrySZReverse falhe.
aku

Acontece que não é o mais rápido em todos os casos :), atualizei minha postagem. Isso ainda precisa ser testado com unicode para correção e velocidade.
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();
    }
}

Resultado

Para tamanho: 10

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

Para tamanho: 100

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

Para o tamanho: 1000

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

Para tamanho: 10000

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

1
Precisa verificar se há uma string vazia Reverse(...). Caso contrário, bom trabalho.
Lara


4

Solução baseada em pilha.

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

Ou

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

4

Teve que enviar um exemplo recursivo:

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
cadeia de comprimento 0 não são tratados
bohdan_trotsenko

Isso não é útil.
user3613932

3

E se:

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

Tem os mesmos problemas de codepoint que outros métodos acima e terá um desempenho muito mais lento do que quando se faz um ToCharArrayprimeiro. O enumerador LINQ também é muito mais lento que Array.Reverse().
Abel

3

Eu criei uma porta C # do Microsoft.VisualBasic.Strings . Não sei por que eles mantêm essas funções úteis (do VB) fora do System.String no Framework, mas ainda no Microsoft.VisualBasic. Mesmo cenário para funções financeiras (por exemplo 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, uma boa adição! Eu apenas tentei string s = "abo\u0327\u0307\u035d\U0001d166cd", que contém a letra oseguida de 3 marcas diacríticas combinadas na BMP e uma marca combinada (MUSICAL SYMBOL COMBINING STEM) do plano astral (não BMP) e as mantém intactas. Mas o método é lento se esses caracteres aparecerem apenas no final de uma cadeia longa, pois ele deve passar duas vezes por toda a matriz.
Abel

3

Desculpe por postar neste tópico antigo. Estou praticando algum código para uma entrevista.

Foi isso que eu criei para o C #. Minha primeira versão antes da refatoração foi horrível.

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

Em contraste com o Array.Reversemétodo abaixo, ele aparece mais rápido com 12 caracteres ou menos na string. Depois de 13 caracteres, o Array.Reverseinício começa a ficar mais rápido e, eventualmente, domina bastante a velocidade. Eu só queria indicar aproximadamente onde a velocidade começa a mudar.

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

    Array.Reverse(charA);

    return new String(charA);
}

Com 100 caracteres na sequência, é mais rápido que minha versão x 4. No entanto, se eu soubesse que as seqüências sempre teriam menos de 13 caracteres, usaria a que criei.

O teste foi realizado com Stopwatche 5000000 iterações. Além disso, não tenho certeza se minha versão lida com substitutos ou situações combinadas de caracteres com Unicodecodificação.


2

O "caminho melhor" depende do que é mais importante para você na sua situação, desempenho, elegância, manutenção etc.

De qualquer forma, aqui está uma abordagem usando 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

Se alguma vez surgiu em uma entrevista e você foi informado de que não pode usar o Array.Reverse, acho que esse pode ser um dos mais rápidos. Ele não cria novas strings e itera apenas mais da metade da matriz (ou seja, iterações 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
Estou certo de que a chamada stringToReverse.ToCharArray () produzirá um tempo de execução O (N).
Marcel Valdez Orozco

Na notação Big-O , o fator não dependente xou, no seu caso, nnão é usado. Seu algoritmo tem desempenho f(x) = x + ½x + C, onde C é uma constante. Como ambos Ce o fator não dependem x, seu algoritmo é O(x). Isso não significa que não será mais rápido para nenhuma entrada de comprimento x, mas seu desempenho depende linearmente do comprimento de entrada. Para responder a @MarcelValdezOrozco, sim, também é O(n), embora copie por pedaços de 16 bytes para melhorar a velocidade (não usa diretamente memcpyo comprimento total).
Abel

2

Se você tiver uma sequência que contenha apenas caracteres ASCII, poderá usar esse método.

    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

Primeiro de tudo o que você precisa entender é que str + = redimensionará sua memória de strings para dar espaço a 1 caractere extra. Tudo bem, mas se você tem, digamos, um livro com 1000 páginas que deseja reverter, isso levará muito tempo para ser executado.

A solução que algumas pessoas podem sugerir está usando o StringBuilder. O que o construtor de strings faz quando você executa um + = é que ele aloca pedaços de memória muito maiores para reter o novo caractere, para que ele não precise realocar toda vez que adicionar um caractere.

Se você realmente deseja uma solução rápida e mínima, sugiro o seguinte:

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

Nesta solução, há uma alocação inicial de memória quando o char [] é inicializado e uma alocação quando o construtor de string cria a string a partir da matriz char.

No meu sistema, fiz um teste para você que inverte uma sequência de 2 750 000 caracteres. Aqui estão os resultados para 10 execuções:

StringBuilder: 190K - 200K ticks

Matriz de caracteres: 130K - 160K ticks

Também executei um teste para String normal + =, mas o abandonei após 10 minutos sem saída.

No entanto, também notei que, para cadeias menores, o StringBuilder é mais rápido; portanto, você terá que decidir sobre a implementação com base na entrada.

Felicidades


não trabalha para😀Les Misérables
Charles

@ Charles Ah, sim, acho que a limitação do conjunto de caracteres.
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);
        }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.