La mejor manera de revertir una cadena

440

Solo tuve que escribir una función inversa de cadena en C # 2.0 (es decir, LINQ no está disponible) y se me ocurrió esto:

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

Personalmente, no estoy loco por la función y estoy convencido de que hay una mejor manera de hacerlo. ¿Esta ahí?

Chico
fuente
51
Sorprendentemente complicado si quieres el apoyo internacional adecuado. Ejemplo: croata / serbio tienen letras de dos caracteres lj, nj, etc. El reverso correcto de "ljudi" es "idulj", NO "idujl". Estoy seguro de que Fare mucho peor cuando se trata de árabe, tailandés, etc.
dbkk
Me pregunto si es más lento concatenar una cadena en lugar de inicializar una matriz temporal y almacenar los resultados en eso, y luego finalmente convertir eso en una cadena.
The Muffin Man
2
Hilo relacionado mucho más nuevo: ¿ Invertir una cadena con caracteres de acento?
Jeppe Stig Nielsen
55
Esta pregunta podría mejorarse definiendo lo que quiere decir con "mejor". ¿Lo más rápido? ¿Más legible? ¿Más confiable en varios casos extremos (comprobaciones nulas, varios idiomas, etc.)? ¿Más mantenible en versiones de C # y .NET?
hipehumano

Respuestas:

608
public static string Reverse( string s )
{
    char[] charArray = s.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}
PeteT
fuente
16
sambo99: no es necesario mencionar unicode: los caracteres en C # son caracteres unicode, no bytes. Xor puede ser más rápido, pero aparte de ser mucho menos legible, eso puede ser incluso lo que Array.Reverse () usa internamente.
Nick Johnson el
27
@Arachnid: en realidad, los caracteres en C # son unidades de código UTF-16; se necesitan dos para representar un personaje suplementario. Consulte jaggersoft.com/csharp_standard/9.4.1.htm .
Bradley Grainger el
44
Sí, sambo99, supongo que tienes razón, pero es un caso bastante raro usar UTF-32. Y XOR es solo más rápido para un rango muy pequeño de valores, la respuesta correcta sería implementar diferentes métodos para diferentes longitudes, supongo. Pero esto es claro y conciso, lo cual es un beneficio en mi opinión.
PeteT
21
Los caracteres de control Unicode hacen que este método sea inútil para conjuntos de caracteres no latinos. Vea la explicación de Jon Skeet, usando un títere de calcetín: codeblog.jonskeet.uk/2009/11/02/… (1/4 hacia abajo), o el video: vimeo.com/7516539
Callum Rogers
20
Espero que no encuentres sustitutos o personajes combinados.
dalle
183

Aquí una solución que invierte correctamente la cadena "Les Mise\u0301rables"como "selbare\u0301siM seL". Esto debería ser igual que selbarésiM seLno selbaŕesiM seL(tenga en cuenta la posición del acento), como sería el resultado de la mayoría de las implementaciones basadas en unidades de código ( Array.Reverse, etc.) o incluso puntos de código (invirtiendo con especial cuidado para los pares sustitutos).

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

(Y ejemplo en vivo aquí: https://ideone.com/DqAeMJ )

Simplemente usa la API .NET para la iteración del clúster de grafemas , que ha estado allí desde siempre, pero parece un poco "oculto" a la vista.

R. Martinho Fernandes
fuente
10
+1 Una de las pocas respuestas correctas, y mucho más elegante y a prueba de futuro que cualquiera de las otras, OMI
sehe
Sin embargo, esto falla para algunas cosas dependientes del entorno local.
R. Martinho Fernandes
77
Es curioso cómo la mayoría de los otros respondedores están tratando de eliminar los enfoques incorrectos. Que representativo.
G. Stoynev
2
En realidad, es significativamente más rápido crear instancias de StringInfo (s), luego iterar a través de SubstringByTextElements (x, 1) y construir una nueva cadena con un StringBuilder.
2
Es un poco extraño que hayas utilizado el ejemplo de Jon Skeet que dio años antes codeblog.jonskeet.uk/2009/11/02/… Les Misérables (aunque Jon no mencionó una solución, solo enumeró los problemas). Qué bueno que se te ocurrió una solución. Tal vez Jon skeet inventó una máquina del tiempo, volvió a 2009 y publicó el ejemplo del problema que utilizó en su solución.
barlop
126

Esta es una pregunta sorprendentemente complicada.

Recomendaría usar Array.Reverse para la mayoría de los casos, ya que está codificado de forma nativa y es muy sencillo de mantener y comprender.

Parece superar a StringBuilder en todos los casos que probé.

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

Hay un segundo enfoque que puede ser más rápido para ciertas longitudes de cadena 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: si desea admitir el juego de caracteres Unicode UTF16 completo, lea esto . Y use la implementación allí en su lugar. Se puede optimizar aún más utilizando uno de los algoritmos anteriores y ejecutando la cadena para limpiarlo después de invertir los caracteres.

Aquí hay una comparación de rendimiento entre el método StringBuilder, Array.Reverse y 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();
        }
    }
}

Aquí están los 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 Xor puede ser más rápido para cadenas cortas.

Sam Saffron
fuente
2
Eso no devuelve una cadena: debe envolver esto en una llamada a "nueva cadena (...)"
Greg Beech
Por cierto ... Acabo de echar un vistazo a la implementación de Array.Reverse, y se hace ingenuamente para los caracteres ... debería ser mucho más rápido que la opción StringBuilder.
Sam Saffron
Qué amable de tu parte, Greg, detener a Sambo para llegar a una mejor solución en lugar de rechazarlo.
DOK
@ dok1 - no lo menciones :) @ sambo99 - ahora estoy intrigado, ¡tendré que sacar un generador de códigos de código mañana y echar un vistazo!
Greg Beech
99
Estos métodos no manejan cadenas que contienen caracteres fuera del plano multilingüe base, es decir, caracteres Unicode> = U + 10000 que se representan con dos caracteres C #. He publicado una respuesta que maneja esas cadenas correctamente.
Bradley Grainger el
52

Si puede usar LINQ (.NET Framework 3.5+), el siguiente código le dará un código breve. No olvides agregar using System.Linq;para tener acceso a Enumerable.Reverse:

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

Notas:

  • no es la versión más rápida, según Martin Niederl, 5,7 veces más lenta que la opción más rápida aquí.
  • este código, como muchas otras opciones, ignora por completo todo tipo de combinaciones de varios caracteres, por lo tanto, limite el uso de tareas y cadenas que no contengan dichos caracteres. Vea otra respuesta en esta pregunta para la implementación que maneja correctamente tales combinaciones.
SGRao
fuente
Eso es alrededor de 5,7 veces más lento que la versión más votada, ¡así que no recomendaría usar esto!
Martin Niederl
2
No es la solución más rápida, pero es útil como una línea.
adrianmp
49

Si la cadena contiene datos Unicode (estrictamente hablando, caracteres que no son BMP), los otros métodos que se han publicado la corromperán, porque no puede intercambiar el orden de las unidades de código sustituto alto y bajo al invertir la cadena. (Puede encontrar más información sobre esto en mi blog ).

El siguiente ejemplo de código invertirá correctamente una cadena que contiene caracteres que no son BMP, por ejemplo, "\ 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);
}
Bradley Grainger
fuente
29
En realidad, los caracteres en C # son unidades de código UTF-16 de 16 bits; se codifica un carácter suplementario con dos de ellos, por lo que esto es necesario,
Bradley Grainger
14
Parece que System.String realmente debería exponer una propiedad HereBeDragons para cadenas que contienen caracteres suplementarios Unicode.
Robert Rossney el
44
@SebastianNegraszus: Eso es correcto: este método simplemente invierte los puntos de código en la cadena. Revertir los grupos de grafemas probablemente sería más "útil" en general (pero ¿cuál es el "uso" de invertir una cadena arbitraria en primer lugar?), Pero no es fácil de implementar con solo los métodos integrados en .NET Framework.
Bradley Grainger el
2
@ Richard: Las reglas para romper los grupos de grafemas son un poco más complicadas que simplemente detectar la combinación de puntos de código; consulte la documentación sobre Grapheme Cluster Boundaries en UAX # 29 para obtener más información.
Bradley Grainger
1
Muy buena informacion! ¿ ALGUIEN tiene una prueba reprobatoria para la prueba Array.Reverse? Y por prueba me refiero a una cadena de muestra, no a una prueba de unidad completa ... Realmente me ayudaría (y a otros) convencer a diferentes personas sobre este tema ...
Andrei Rînea
25

Ok, en aras de "no te repitas", ofrezco la siguiente solución:

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

Tengo entendido que esta implementación, disponible de forma predeterminada en VB.NET, maneja correctamente los caracteres Unicode.

richardtallent
fuente
11
Esto solo maneja sustitutos correctamente. Se estropea combinando marcas: ideone.com/yikdqX .
R. Martinho Fernandes
17

Greg Beech publicó una unsafeopción que de hecho es lo más rápida posible (es una inversión en el lugar); pero, como indicó en su respuesta, es una idea completamente desastrosa .

Dicho esto, me sorprende que haya tanto consenso que Array.Reversees el método más rápido. Todavía hay un unsafeenfoque que devuelve una copia invertida de una cadena (sin travesuras de inversión en el lugar) significativamente más rápido que el Array.Reversemétodo para cadenas pequeñas:

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

Aquí hay algunos resultados de referencia .

Puede ver que la ganancia de rendimiento se reduce y luego desaparece con el Array.Reversemétodo a medida que las cadenas se hacen más grandes. Sin embargo, para cadenas de tamaño pequeño a mediano, es difícil superar este método.

Dan Tao
fuente
2
StackOverflow en cadenas grandes.
Raz Megrelidze
@rezomegreldize: Sí, eso sucederá;)
Dan Tao
15

La respuesta fácil y agradable es usar el Método de Extensión:

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

y aquí está la salida:

string Answer = "12345".Inverse(); // = "54321"
Mehdi Khademloo
fuente
Reverse()y ToArray()están en el orden incorrecto en su muestra de código.
Chris Walsh
¿Para qué sirve el @?
user5389726598465
2
@ user5389726598465 Vea este enlace: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Debido a que 'base' es una palabra clave en C #, debe tener el prefijo @ para que el compilador de C # lo interprete como un identificador
Dyndrilliac
14

Si quieres jugar un juego realmente peligroso, esta es, con mucho, la forma más rápida que existe (alrededor de cuatro veces más rápido que el Array.Reversemétodo). Es un reverso in situ usando punteros.

Tenga en cuenta que realmente no lo recomiendo para ningún uso, nunca ( eche un vistazo aquí por algunas razones por las que no debe usar este método ), pero es interesante ver que se puede hacer, y que las cadenas no son realmente inmutables una vez que active el código inseguro.

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;
    }
}
Greg Beech
fuente
Estoy bastante seguro de que este proporcione resultados incorrectos para cadenas UTF16, lo que realmente está pidiendo problemas :)
Sam azafrán
Hola, debería vincular a esta publicación en este stackoverflow.com/questions/229346/… , como dije antes, esto realmente está pidiendo problemas ...
Sam Saffron
Esto puede ser completamente malvado y desaconsejado (como usted mismo lo reconoce), pero todavía hay una forma de alto rendimiento para revertir una cadena usando un unsafecódigo que no es malo y que en muchos casos aún late Array.Reverse. Mira mi respuesta.
Dan Tao
13

Echa un vistazo a la entrada de wikipedia aquí . Implementan el método de extensión String.Reverse. Esto le permite escribir código como este:

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

También usan la combinación ToCharArray / Reverse que sugieren otras respuestas a esta pregunta. El código fuente se ve así:

public static string Reverse(this string input)
{
    char[] chars = input.ToCharArray();
    Array.Reverse(chars);
    return new String(chars);
}
Mike Thompson
fuente
Eso es maravilloso, excepto que los métodos de extensión no se introdujeron en c # 2.0.
Kobi el
11

En primer lugar, no necesita llamar ToCharArrayya que una cadena ya puede indexarse ​​como una matriz de caracteres, por lo que esto le ahorrará una asignación.

La siguiente optimización es usar a StringBuilderpara evitar asignaciones innecesarias (ya que las cadenas son inmutables, concatenandolas hace una copia de la cadena cada vez). Para optimizar aún más esto, preestablecemos la longitud de StringBuildermodo que no necesite expandir su búfer.

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: Datos de rendimiento

Probé esta función y la función usando Array.Reverseel siguiente programa simple, donde Reverse1es una función y Reverse2es la otra:

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

Resulta que para cadenas cortas el Array.Reversemétodo es aproximadamente el doble de rápido que el anterior, y para cadenas más largas la diferencia es aún más pronunciada. Entonces, dado que el Array.Reversemétodo es más simple y rápido, te recomiendo que lo uses en lugar de este. Lo dejo aquí solo para mostrar que no es la forma en que debes hacerlo (¡para mi sorpresa!)

Greg Beech
fuente
No almacenar el texto. La longitud en una variable da un poco más de velocidad al hacer referencia a esto a través de un objeto
David Robbins
10

Intenta usar Array.


public string Reverse(string str)
{
    char[] array = str.ToCharArray();
    Array.Reverse(array);
    return new string(array);
}
Mike dos
fuente
Esto es increíblemente rápido.
Michael Stum
¿Por qué el voto negativo? Sin discutirlo, pero prefiero aprender de mis errores.
Mike Two
No puede manejar la combinación de puntos de código entre muchas otras cosas.
Mooing Duck
@MooingDuck: gracias por explicarlo, pero no sé qué quieres decir con puntos de código. También podría elaborar sobre "muchas otras cosas".
Mike Two
@MooingDuck Busqué puntos de código. Si. Estás en lo correcto. No maneja puntos de código. Es difícil determinar todos los requisitos para una pregunta tan simple. Gracias por los comentarios
Mike Two
10
public static string Reverse(string input)
{
    return string.Concat(Enumerable.Reverse(input));
}

Por supuesto, puede extender la clase de cadena con el método inverso

public static class StringExtensions
{
    public static string Reverse(this string input)
    {
        return string.Concat(Enumerable.Reverse(input));
    }
}
Vlad Bezden
fuente
Enumerable.Reverse(input)es igual ainput.Reverse()
fubo
8

"Mejor" puede depender de muchas cosas, pero aquí hay algunas alternativas cortas más ordenadas de rápido a lento:

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̽"  👍
Slai
fuente
8

A partir de .NET Core 2.1, hay una nueva forma de invertir una cadena utilizando string.Create método.

Tenga en cuenta que esta solución no maneja los caracteres combinados Unicode, etc. correctamente, ya que "Les Mise \ u0301rables" se convertiría en "selbarésiM seL". El otro responde para una mejor solución.

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

Esto esencialmente copia los caracteres de inputa una nueva cadena e invierte la nueva cadena en el lugar.

¿Por qué es string.Createútil?

Cuando creamos una cadena a partir de una matriz existente, se asigna una nueva matriz interna y se copian los valores. De lo contrario, sería posible mutar una cadena después de su creación (en un entorno seguro). Es decir, en el siguiente fragmento tenemos que asignar una matriz de longitud 10 dos veces, una como el búfer y otra como la matriz interna de la cadena.

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

string.Createesencialmente nos permite manipular la matriz interna durante el tiempo de creación de la cadena. Esto es, ya no necesitamos un búfer y, por lo tanto, podemos evitar asignar esa matriz de caracteres.

Steve Gordon ha escrito sobre esto con más detalle aquí . También hay un artículo sobre MSDN .

¿Cómo usarlo string.Create?

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

El método toma tres parámetros:

  1. La longitud de la cadena para crear,
  2. los datos que desea usar para crear dinámicamente la nueva cadena,
  3. y un delegado que crea la cadena final a partir de los datos, donde el primer parámetro apunta a la charmatriz interna de la nueva cadena y el segundo es el dato (estado) al que pasó string.Create.

Dentro del delegado podemos especificar cómo se crea la nueva cadena a partir de los datos. En nuestro caso, simplemente copiamos los caracteres de la cadena de entrada a los Spanutilizados por la nueva cadena. Luego revertimos elSpan y, por lo tanto, se invierte toda la cadena.

Puntos de referencia

Para comparar mi forma propuesta de invertir una cadena con la respuesta aceptada, he escrito dos puntos de referencia utilizando 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);
}

Aquí están los resultados en mi 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 puede ver, con ReverseWithStringCreatesolo asignamos la mitad de la memoria utilizada por el ReverseWithArraymétodo.

Flogex
fuente
Es mucho más rápido que el reverso de Linq
code4j
7

No te molestes con una función, solo hazlo en su lugar. Nota: La segunda línea arrojará una excepción de argumento en la ventana Inmediato de algunas versiones VS.

string s = "Blah";
s = new string(s.ToCharArray().Reverse().ToArray()); 
BH
fuente
1
Algún tipo se tomó el tiempo de rechazar cada respuesta (incluida la mía) sin explicar por qué.
Marcel Valdez Orozco
Esto no está realmente en su lugar, ya que está creando unnew string
mbadawi23
5

Perdón por la publicación larga, pero esto puede ser interesante

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
aku
fuente
Agregué una comparación similar en mi publicación, es un wiki de la comunidad, por lo que debería poder editar. El rendimiento realmente depende de la longitud de la cadena y del algoritmo, sería interesante graficarlo. Sigo pensando que Array. El reverso será el más rápido en todos los casos ...
Sam Saffron
"será el más rápido en todos los casos" cuando la función mágica TrySZReverse (se usa en la implementación inversa) falla, Array. Los retrocesos inversos a la implementación simple que involucra el boxeo, por lo que mi método ganará. Sin embargo, no sé cuál es una condición para que TrySZReverse falle.
aku
Resulta que no es el más rápido en todos los casos :), actualicé mi publicación. Esto aún debe probarse con Unicode tanto para la corrección como para la velocidad.
Sam Saffron el
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();
    }
}

Salida

Para talla: 10

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

Para el tamaño: 100

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

Para el tamaño: 1000

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

Para el tamaño: 10000

Naive Ticks: 32
Efficient linear Ticks: 28
Recursive Ticks: 84808
DotNet Reverse Ticks: 33
Marcel Valdez Orozco
fuente
1
Necesito verificar si hay una cadena vacía Reverse(...). De lo contrario, buen trabajo.
Lara
5

La forma más simple:

string reversed = new string(text.Reverse().ToArray());
Shady Sirhan
fuente
Yo uso la misma oración
VhsPiceros
4

Solución basada en pila.

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

O

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

Tuve que presentar un ejemplo 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));
}
JPrescottSanders
fuente
1
cadena de longitud 0 no se maneja
bohdan_trotsenko
Esto no es útil.
user3613932
3

Qué tal si:

    private string Reverse(string stringToReverse)
    {
        char[] rev = stringToReverse.Reverse().ToArray();
        return new string(rev); 
    }
Zamir
fuente
Tiene los mismos problemas de puntos de código que otros métodos anteriores y funcionará mucho más lentamente que cuando se realiza por ToCharArrayprimera vez. El enumerador LINQ también es mucho más lento que Array.Reverse().
Abel
3

He creado un puerto C # de Microsoft.VisualBasic.Strings . No estoy seguro de por qué mantienen funciones tan útiles (desde VB) fuera del System.String en Framework, pero aún bajo Microsoft.VisualBasic. Mismo escenario para las funciones financieras (por ejemplo 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();
}
natenho
fuente
+1, una buena adición! Acabo de probarlo string s = "abo\u0327\u0307\u035d\U0001d166cd", que contiene la letra oseguida de 3 marcas diacríticas combinadas en el BMP y una marca combinada (TIPO COMBINADO DE SÍMBOLO MUSICAL) del plano astral (no BMP) y las mantiene intactas. Pero el método es lento si dichos caracteres solo aparecen al final de una cadena larga, ya que tiene que ir dos veces sobre toda la matriz.
Abel
3

Perdón por publicar en este viejo hilo. Estoy practicando un código para una entrevista.

Esto fue lo que se me ocurrió para C #. Mi primera versión antes de refactorizar fue horrible.

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

En contraste con el Array.Reversesiguiente método, parece más rápido con 12 caracteres o menos en la cadena. Después de 13 personajes, Array.Reversecomienza a ser más rápido y finalmente domina bastante en velocidad. Solo quería señalar aproximadamente dónde comienza a cambiar la velocidad.

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

    Array.Reverse(charA);

    return new String(charA);
}

Con 100 caracteres en la cadena, es más rápido que mi versión x 4. Sin embargo, si supiera que las cadenas siempre tendrían menos de 13 caracteres, usaría la que hice.

Las pruebas se realizaron con Stopwatchy 5000000 iteraciones. Además, no estoy seguro de si mi versión maneja sustitutos o situaciones de caracteres combinados con Unicodecodificación.

Jason Ausborn
fuente
2

"Mejor manera" depende de lo que sea más importante para usted en su situación, rendimiento, elegancia, facilidad de mantenimiento, etc.

De todos modos, aquí hay un enfoque usando Array.

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

string reversed = new string(charArray);
Ceniza
fuente
2

Si alguna vez surgió en una entrevista y le dijeron que no puede usar Array. Sin embargo, creo que este podría ser uno de los más rápidos. No crea nuevas cadenas e itera solo más de la mitad de la matriz (es decir, iteraciones 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);
    }
mike01010
fuente
2
Estoy bastante seguro de que la llamada stringToReverse.ToCharArray () producirá un tiempo de ejecución O (N).
Marcel Valdez Orozco
En la notación Big-O , el factor que no depende de x, o en su caso, nno se utiliza. Su algoritmo tiene rendimiento f(x) = x + ½x + C, donde C es algo constante. Como ambos Cy el factor no dependen x, su algoritmo sí lo es O(x). Eso no significa que no será más rápido para ninguna entrada de longitud x, pero su rendimiento depende linealmente de la longitud de entrada. Para responder a @MarcelValdezOrozco, sí, también lo es O(n), aunque copia fragmentos de 16 bytes para mejorar la velocidad (no utiliza una recta memcpyen la longitud total).
Abel
2

Si tiene una cadena que solo contiene caracteres ASCII, puede usar este 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);
    }
Raz Megrelidze
fuente
2

En primer lugar, lo que debe comprender es que str + = redimensionará su memoria de cadena para dejar espacio para 1 carácter adicional. Esto está bien, pero si tiene, por ejemplo, un libro con 1000 páginas que desea revertir, llevará mucho tiempo ejecutarlo.

La solución que algunas personas podrían sugerir es usar StringBuilder. Lo que hace el generador de cadenas cuando realiza un + = es que asigna trozos de memoria mucho más grandes para contener el nuevo carácter, de modo que no necesita realizar una reasignación cada vez que agrega un carácter.

Si realmente quieres una solución rápida y mínima, te sugiero lo siguiente:

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

En esta solución, hay una asignación de memoria inicial cuando se inicializa char [] y una asignación cuando el constructor de cadenas construye la cadena a partir de la matriz de caracteres.

En mi sistema, ejecuté una prueba para usted que invierte una cadena de 2 750 000 caracteres. Aquí están los resultados de 10 ejecuciones:

StringBuilder: 190K - 200K garrapatas

Char Array: 130K - 160K garrapatas

También ejecuté una prueba para String + = normal pero la abandoné después de 10 minutos sin salida.

Sin embargo, también noté que para cadenas más pequeñas, StringBuilder es más rápido, por lo que tendrá que decidir la implementación en función de la entrada.

Salud

Reasurria
fuente
no funciona para 😀Les Misérables
Charles
@ Charles Ah, sí, supongo que hay una limitación de 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;
}
ddagsan
fuente
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);
        }
Shrini
fuente