Il modo migliore per invertire una stringa


441

Ho appena dovuto scrivere una funzione di inversione di stringa in C # 2.0 (cioè LINQ non disponibile) e ho pensato a questo:

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 non vado pazzo per la funzione e sono convinto che ci sia un modo migliore per farlo. È lì?


51
Sorprendentemente complicato se si desidera un adeguato supporto internazionale. Esempio: croato / serbo hanno lettere a due caratteri lj, nj ecc. Il contrario corretto di "ljudi" è "idulj", NON "idujl". Sono sicuro che andrai molto peggio quando si tratta di arabo, tailandese ecc.
dbkk

Mi chiedo se è più lento concaticare una stringa invece di inizializzare un array temporaneo e archiviare i risultati in quello, e infine convertirlo in una stringa?
The Muffin Man


5
Questa domanda potrebbe essere migliorata definendo cosa intendi per "migliore". Più veloce? Più leggibile? Più affidabile su vari casi limite (controlli null, più lingue, ecc.)? Più gestibile tra le versioni di C # e .NET?
hypehuman

Risposte:


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

16
sambo99: Non è necessario menzionare Unicode: i caratteri in C # sono caratteri Unicode, non byte. Xor potrebbe essere più veloce, ma a parte essere molto meno leggibile, potrebbe anche essere ciò che Array.Reverse () usa internamente.
Nick Johnson,

27
@Arachnid: In realtà, i caratteri in C # sono unità di codice UTF-16; ne servono due per rappresentare un carattere supplementare. Vedi jaggersoft.com/csharp_standard/9.4.1.htm .
Bradley Grainger,

4
Sì sambo99 Suppongo che tu abbia ragione, ma è un caso abbastanza raro usare UTF-32. E XOR è solo più veloce per un intervallo molto piccolo di valori, suppongo che la risposta corretta sarebbe implementare metodi diversi per lunghezze diverse. Ma questo è chiaro e conciso, il che è un vantaggio secondo me.
PeteT,

21
I caratteri di controllo Unicode rendono questo metodo inutile per set di caratteri non latini. Vedi la spiegazione di Jon Skeet, usando un burattino calzino: codeblog.jonskeet.uk/2009/11/02/… (1/4 in giù), o il video: vimeo.com/7516539
Callum Rogers

20
Spero di non incontrare surrogati o combinare personaggi.
dalle

183

Qui una soluzione che inverte correttamente la stringa "Les Mise\u0301rables"come "selbare\u0301siM seL". Questo dovrebbe essere visualizzato esattamente come selbarésiM seL, non selbaŕesiM seL(notare la posizione dell'accento), come il risultato della maggior parte delle implementazioni basate su unità di codice ( Array.Reverse, ecc.) O persino punti di codice (invertendo con particolare attenzione le coppie surrogate).

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 esempio di corsa dal vivo qui: https://ideone.com/DqAeMJ )

Utilizza semplicemente l' API .NET per l'iterazione del cluster grapheme , che esiste da sempre, ma sembra un po '"nascosta" alla vista.


10
+1 Una delle pochissime risposte esatte e una prova molto più elegante e futura di tutte le altre, IMO
visto il

Tuttavia, ciò non riesce per alcune cose dipendenti dalla locale.
R. Martinho Fernandes,

7
È buffo come la maggior parte degli altri risponditori stia cercando di eliminare ms da approcci altrimenti errati. Quanto rappresentativo.
G. Stoynev,

2
In realtà è molto più veloce istanziare StringInfo (s), quindi scorrere attraverso SubstringByTextElements (x, 1) e creare una nuova stringa con StringBuilder.

2
È un po 'strano che tu abbia usato l'esempio di Jon Skeet che ha fornito anni prima codeblog.jonskeet.uk/2009/11/02/… Les Misérables (sebbene Jon non abbia menzionato una soluzione, ha solo elencato i problemi). Bene, hai trovato una soluzione. Forse Jon Skeet ha inventato una macchina del tempo, è tornato al 2009 e ha pubblicato l'esempio del problema che hai usato nella tua soluzione.
barlop

126

Questa si sta rivelando una domanda sorprendentemente complicata.

Consiglierei di usare Array.Reverse per la maggior parte dei casi poiché è codificato in modo nativo ed è molto semplice da mantenere e comprendere.

Sembra superare StringBuilder in tutti i casi che ho testato.

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

Esiste un secondo approccio che può essere più veloce per determinate lunghezze di stringa che utilizza 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 si desidera supportare il set di caratteri Unicode UTF16 completo, leggere questo . E usa invece l'implementazione lì. Può essere ulteriormente ottimizzato utilizzando uno dei suddetti algoritmi ed eseguendo la stringa per ripulirlo dopo che i caratteri sono stati invertiti.

Ecco un confronto delle prestazioni tra il metodo 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();
        }
    }
}

Ecco i risultati:

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.

Sembra che Xor possa essere più veloce per stringhe brevi.


2
Ciò non restituisce una stringa - è necessario racchiuderla in una chiamata a "nuova stringa (...)"
Greg Beech,

A proposito .. Ho appena dato un'occhiata all'implementazione di Array.Reverse, ed è fatto in modo ingenuo per i caratteri ... dovrebbe essere molto più veloce dell'opzione StringBuilder.
Sam Saffron,

Che bello da parte tua, Greg, fermare Sambo per arrivare a una soluzione migliore invece di votarlo a fondo.
DOK,

@ dok1 - non dirlo :) @ sambo99 - ora sono incuriosito, domani dovrò tirar fuori un profiler di codice e dare un'occhiata!
Greg Beech,

9
Questi metodi non gestiscono stringhe contenenti caratteri al di fuori del Piano multilingue di base, ovvero caratteri Unicode> = U + 10000 che sono rappresentati con due caratteri C #. Ho pubblicato una risposta che gestisce correttamente tali stringhe.
Bradley Grainger,

52

Se è possibile utilizzare LINQ (.NET Framework 3.5+) rispetto a un solo liner, verrà visualizzato un codice funzione. Non dimenticare di aggiungere using System.Linq;per avere accesso a Enumerable.Reverse:

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

Appunti:

  • non la versione più veloce - secondo Martin Niederl 5,7 volte più lento della scelta più veloce qui.
  • questo codice, come molte altre opzioni, ignora completamente ogni tipo di combinazione multi-carattere, quindi limita l'uso di compiti e stringhe di compiti che non contengono tali caratteri. Vedere un'altra risposta in questa domanda per l'implementazione che gestisce correttamente tali combinazioni.

Questo è circa 5,7 volte più lento della versione più votata, quindi non consiglierei di usarlo!
Martin Niederl,

2
Non è la soluzione più veloce, ma utile come one-liner.
Adrianmp,

49

Se la stringa contiene dati Unicode (in senso stretto, caratteri non BMP) gli altri metodi che sono stati pubblicati la corromperanno, poiché non è possibile scambiare l'ordine di unità di codice surrogato alto e basso quando si inverte la stringa. (Ulteriori informazioni al riguardo sono disponibili sul mio blog .)

L'esempio di codice seguente invertirà correttamente una stringa che contiene caratteri non BMP, ad esempio "\ U00010380 \ U00010381" (Ugaritic Letter Alpa, Ugaritic Letter Beta).

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

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

    return new string(output);
}

29
In realtà, i caratteri in C # sono unità di codice UTF-16 a 16 bit; un carattere supplementare viene codificato usando due di essi, quindi questo è necessario,
Bradley Grainger,

14
Sembra che System.String dovrebbe davvero esporre una proprietà HereBeDragons per le stringhe che contengono caratteri supplementari Unicode.
Robert Rossney,

4
@SebastianNegraszus: Esatto: questo metodo inverte i punti di codice nella stringa. Inversione dei grappoli sarebbe probabilmente più "utile" in generale (ma qual è l '"uso" di invertire una stringa arbitraria in primo luogo?), Ma non è facile da implementare con solo i metodi integrati in .NET Framework.
Bradley Grainger,

2
@Richard: le regole per rompere i cluster grapheme sono un po 'più complicate rispetto al semplice rilevamento della combinazione di punti di codice; consultare la documentazione sui confini del cluster Grapheme in UAX # 29 per ulteriori informazioni.
Bradley Grainger,

1
Informazioni molto buone! Ha chiunque ha un test in mancanza per il test Array.reverse? E per test intendo una stringa campione non un intero test unitario ... Sarebbe davvero di aiuto a me (e ad altri) convincere persone diverse su questo problema.
Andrei Rînea,

25

Ok, nell'interesse di "non ripeterti", offro la seguente soluzione:

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

La mia comprensione è che questa implementazione, disponibile per impostazione predefinita in VB.NET, gestisce correttamente i caratteri Unicode.


11
Questo gestisce solo i surrogati correttamente. Si incasina combinando marchi: ideone.com/yikdqX .
R. Martinho Fernandes,

17

Greg Beech ha pubblicato unsafeun'opzione che è davvero la più veloce possibile (è un'inversione sul posto); ma, come ha indicato nella sua risposta, è un'idea completamente disastrosa .

Detto questo, sono sorpreso che ci sia così tanto consenso che Array.Reversesia il metodo più veloce. Esiste ancora un unsafeapproccio che restituisce una copia invertita di una stringa (senza shenanigans di inversione sul posto) in modo significativamente più veloce rispetto al Array.Reversemetodo per stringhe di piccole dimensioni:

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

Ecco alcuni risultati di riferimento .

Puoi vedere che il guadagno in termini di prestazioni si riduce e poi scompare rispetto al Array.Reversemetodo man mano che le stringhe diventano più grandi. Per le stringhe di piccole e medie dimensioni, tuttavia, è difficile battere questo metodo.


2
StackOverflow su stringhe di grandi dimensioni.
Raz Megrelidze,

@rezomegreldize: Sì, succederà;)
Dan Tao,

15

La risposta semplice e piacevole sta usando il metodo di estensione:

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

ed ecco l'output:

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

Reverse()e ToArray()sono nell'ordine sbagliato nell'esempio di codice.
Chris Walsh,

A quale scopo serve @?
user5389726598465

2
@ user5389726598465 Vedi questo link: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Poiché 'base' è una parola chiave in C #, deve essere preceduto da @ per il compilatore C # per interpretarlo come un identificatore.
Dyndrilliac,

14

Se vuoi giocare a un gioco davvero pericoloso, questo è di gran lunga il modo più veloce che ci sia (circa quattro volte più veloce del Array.Reversemetodo). È un rovescio sul posto che utilizza i puntatori.

Nota che non lo consiglio davvero per nessun uso, mai (dai un'occhiata qui per alcuni motivi per cui non dovresti usare questo metodo ), ma è interessante vedere che può essere fatto e che le stringhe non sono davvero immutabili una volta attivato il codice non sicuro.

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

Sono abbastanza sicuro che questo restituirà risultati errati per le stringhe utf16, sta davvero chiedendo problemi :)
Sam Saffron,

Ciao, dovresti linkare questo post su questo stackoverflow.com/questions/229346/… , come ho detto prima, questo sta davvero chiedendo problemi ...
Sam Saffron,

Questo può essere completamente malvagio e sconsigliato (come tu stesso ammetti), ma c'è ancora un modo ad alte prestazioni per invertire una stringa usando un unsafecodice che non è male e batte ancoraArray.Reverse in molti casi. Dai un'occhiata alla mia risposta.
Dan Tao,

13

Dai un'occhiata alla voce di Wikipedia qui . Implementano il metodo di estensione String.Reverse. Ciò ti consente di scrivere codice in questo modo:

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

Usano anche la combinazione ToCharArray / Reverse suggerita da altre risposte a questa domanda. Il codice sorgente è simile al seguente:

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

È meraviglioso, tranne che i metodi di estensione non sono stati introdotti in c # 2.0.
Kobi,

11

Innanzitutto non è necessario chiamare ToCharArraypoiché una stringa può già essere indicizzata come array di caratteri, quindi questo ti farà risparmiare un'allocazione.

La prossima ottimizzazione consiste nell'utilizzare a StringBuilderper evitare allocazioni non necessarie (poiché le stringhe sono immutabili, concatenandole crea una copia della stringa ogni volta). Per ottimizzare ulteriormente questo, abbiamo preimpostato la lunghezza di StringBuildercosì non sarà necessario espandere il suo 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();
}

Modifica: dati sulle prestazioni

Ho testato questa funzione e la funzione utilizzando Array.Reversecon il seguente semplice programma, dove Reverse1è una funzione ed Reverse2è l'altra:

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

Si scopre che per le stringhe brevi il Array.Reversemetodo è circa due volte più veloce di quello sopra e per le stringhe più lunghe la differenza è ancora più pronunciata. Quindi, dato che il Array.Reversemetodo è sia più semplice che più veloce, ti consiglio di usarlo piuttosto che questo. Lascio questo qui solo per dimostrare che non è il modo in cui dovresti farlo (con mia grande sorpresa!)


Memorizzare il testo. La lunghezza in una variabile darebbe un po 'più di velocità mentre fai riferimento a questo tramite un oggetto?
David Robbins,

10

Prova a usare Array.Reverse


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

Questo è incredibilmente veloce.
Michael Stum

Perché il voto negativo? Non litigare, ma preferirei imparare dai miei errori.
Mike, 2

Non riesce a gestire la combinazione di punti di codice tra molte altre cose.
Mooing Duck

@MooingDuck - grazie per avermi spiegato, ma non so cosa intendi per punti di codice. Potresti anche approfondire "molte altre cose".
Mike Two

@MooingDuck Ho cercato punti di codice. Sì. Hai ragione. Non gestisce i punti di codice. È difficile determinare tutti i requisiti per una domanda così semplice. Grazie per il feedback
Mike Two

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

Ovviamente puoi estendere la classe di stringhe con il metodo Reverse

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

Enumerable.Reverse(input)è uguale ainput.Reverse()
fubo,

8

"Best" può dipendere da molte cose, ma qui ci sono alcune alternative più brevi ordinate da veloce 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̽"  👍

8

A partire da .NET Core 2.1 c'è un nuovo modo per invertire una stringa usando il string.Createmetodo

Nota che questa soluzione non gestisce correttamente la combinazione di caratteri Unicode ecc., Poiché "Les Mise \ u0301rables" verrebbe convertito in "selbarésiM seL". L' altro risponde per una soluzione migliore.

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

Questo essenzialmente copia i caratteri di inputuna nuova stringa e inverte la nuova stringa sul posto.

Perché è string.Createutile

Quando creiamo una stringa da un array esistente, viene allocato un nuovo array interno e i valori vengono copiati. Altrimenti, sarebbe possibile mutare una stringa dopo la sua creazione (in un ambiente sicuro). Cioè, nel frammento seguente dobbiamo allocare una matrice di lunghezza 10 due volte, una come buffer e una come matrice interna della stringa.

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

string.Createessenzialmente ci consente di manipolare l'array interno durante il tempo di creazione della stringa. Ciò significa che non abbiamo più bisogno di un buffer e possiamo quindi evitare di allocare quell'array di caratteri.

Steve Gordon ne ha scritto più in dettaglio qui . C'è anche un articolo su MSDN .

Come usare string.Create?

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

Il metodo accetta tre parametri:

  1. La lunghezza della stringa da creare,
  2. i dati che desideri utilizzare per creare dinamicamente la nuova stringa,
  3. e un delegato che crea la stringa finale dai dati, in cui il primo parametro punta alla charmatrice interna della nuova stringa e il secondo sono i dati (stato) a cui si è passati string.Create.

All'interno del delegato possiamo specificare come viene creata la nuova stringa dai dati. Nel nostro caso, copiamo solo i caratteri della stringa di input in quelli Spanusati dalla nuova stringa. Quindi invertiamo Spane quindi l'intera stringa viene invertita.

Punti di riferimenti

Per confrontare il mio modo proposto di invertire una stringa con la risposta accettata, ho scritto due benchmark usando 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);
}

Ecco i risultati sulla mia macchina:

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

Come puoi vedere, con ReverseWithStringCreateallociamo solo metà della memoria utilizzata dal ReverseWithArraymetodo.


È molto più veloce del Linq reverse
code4j

7

Non preoccuparti di una funzione, fallo sul posto. Nota: la seconda riga genererà un'eccezione argomento nella finestra Immediata di alcune versioni VS.

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

1
Qualcuno si è preso il tempo di sottovalutare ogni risposta (compresa la mia) senza spiegare il perché.
Marcel Valdez Orozco,

Questo non è proprio a posto, dato che stai creando unnew string
mbadawi23

5

Ci scusiamo per lunghi post, ma questo potrebbe essere 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");            
        }
    }
}

risultati:

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

Ho aggiunto un confronto simile nel mio post, è un wiki della comunità, quindi dovresti essere in grado di modificarlo. Le prestazioni dipendono davvero dalla lunghezza della stringa e dall'algoritmo, sarebbe interessante rappresentarla graficamente. Penso ancora che Array.Reverse sarà il più veloce in tutti i casi ...
Sam Saffron,

"sarà il più veloce in tutti i casi" quando la funzione magica TrySZReverse (utilizzata nell'implementazione inversa) fallisce, Array.Invia i fallback alla semplice implementazione che coinvolge la boxe, quindi il mio metodo vincerà. Tuttavia non so quale sia una condizione per far fallire TrySZReverse.
Aku,

Risulta non è il più veloce in tutti i casi :), ho aggiornato il mio post. Questo deve ancora essere testato con Unicode sia per correttezza che per velocità.
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();
    }
}

Produzione

Per taglia: 10

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

Per taglia: 100

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

Per taglia: 1000

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

Per dimensioni: 10000

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

1
È necessario verificare la presenza di stringhe vuote Reverse(...). Altrimenti, buon lavoro.
Lara,


4

Soluzione basata su stack.

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

4

Ho dovuto presentare un esempio ricorsivo:

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
la stringa di Lunghezza 0 non viene gestita
bohdan_trotsenko

Questo non è utile
user3613932

3

Che ne dite di:

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

Ha gli stessi problemi con i punti di codice degli altri metodi precedenti e funzionerà molto più lentamente rispetto a quando si esegue un ToCharArrayprimo. L'enumeratore LINQ è anche molto più lento di Array.Reverse().
Abele,

3

Ho creato una porta C # da Microsoft.VisualBasic.Strings . Non sono sicuro del perché mantengano tali utili funzioni (da VB) al di fuori del System.String in Framework, ma ancora in Microsoft.VisualBasic. Stesso scenario per funzioni finanziarie (ad es 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, una bella aggiunta! Ho appena provato con string s = "abo\u0327\u0307\u035d\U0001d166cd", che contiene la lettera oseguita da 3 segni combinati diacritici nel BMP e un segno combinato (MUSICAL SYMBOL COMBINING STEM) dal piano astrale (non BMP) e li mantiene intatti. Ma il metodo è lento se tali caratteri compaiono solo alla fine di una lunga stringa, poiché deve andare due volte sull'intero array.
Abele,

3

Ci scusiamo per la pubblicazione su questa vecchia discussione. Sto praticando un po 'di codice per un'intervista.

Questo è quello che mi è venuto in mente per C #. La mia prima versione prima del refactoring era orribile.

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

In contrasto con il Array.Reversemetodo seguente, appare più veloce con 12 caratteri o meno nella stringa. Dopo 13 personaggi, Array.Reverseinizia a diventare più veloce e alla fine domina abbastanza pesantemente sulla velocità. Volevo solo sottolineare approssimativamente dove la velocità inizia a cambiare.

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

    Array.Reverse(charA);

    return new String(charA);
}

Con 100 caratteri nella stringa, è più veloce della mia versione x 4. Tuttavia, se sapessi che le stringhe sarebbero sempre di meno di 13 caratteri, userei quella che ho creato.

Il test è stato eseguito con Stopwatche 5000000 iterazioni. Inoltre, non sono sicuro che la mia versione gestisca Surrogati o situazioni di personaggi combinati con la Unicodecodifica.


2

Il "modo migliore" dipende da ciò che è più importante per te nella tua situazione, prestazioni, eleganza, manutenibilità ecc.

Ad ogni modo, ecco un approccio che utilizza 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 dovesse mai venire in un'intervista e ti è stato detto che non puoi usare Array.Inverso, penso che questo potrebbe essere uno dei più veloci. Non crea nuove stringhe e scorre solo oltre la metà dell'array (ovvero iterazioni 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
Sono abbastanza certo che la chiamata stringToReverse.ToCharArray () produrrà un tempo di esecuzione O (N).
Marcel Valdez Orozco,

Nella notazione Big-O , il fattore non dipendente xo, nel tuo caso, nnon viene utilizzato. Il tuo algoritmo ha prestazioni f(x) = x + ½x + C, dove C è una costante. Poiché entrambi Ce il fattore non dipendono da x, il tuo algoritmo lo è O(x). Ciò non significa che non sarà più veloce per qualsiasi input di lunghezza x, ma le sue prestazioni dipendono linearmente dalla lunghezza dell'input. Per rispondere a @MarcelValdezOrozco, sì, lo è anche O(n), anche se copia per blocchi di 16 byte per migliorare la velocità (non utilizza un rettilineo memcpysulla lunghezza totale).
Abele,

2

Se hai una stringa che contiene solo caratteri ASCII, puoi usare questo metodo.

    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

Prima di tutto quello che devi capire è che str + = ridimensionerà la memoria della stringa per fare spazio per 1 carattere extra. Va bene, ma se hai, per esempio, un libro con 1000 pagine che vuoi invertire, ci vorrà molto tempo per essere eseguito.

La soluzione che alcune persone potrebbero suggerire sta usando StringBuilder. Ciò che fa il builder di stringhe quando si esegue un + = è che alloca blocchi di memoria molto più grandi per contenere il nuovo carattere, in modo che non sia necessario effettuare una riallocazione ogni volta che si aggiunge un carattere.

Se vuoi davvero una soluzione veloce e minima ti suggerirei quanto segue:

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

In questa soluzione c'è un'allocazione di memoria iniziale quando il char [] è inizializzato e un'allocazione quando il costruttore di stringhe costruisce la stringa dall'array char.

Sul mio sistema ho eseguito un test per te che inverte una stringa di 2 750 000 caratteri. Ecco i risultati per 10 esecuzioni:

StringBuilder: 190K - 200K tick

Array di caratteri: 130K - 160K tick

Ho anche eseguito un test per String normale = = ma l'ho abbandonato dopo 10 minuti senza output.

Tuttavia, ho anche notato che StringBuilder è più veloce per stringhe più piccole, quindi dovrai decidere l'implementazione in base all'input.

Saluti


non funziona per😀Les Misérables
Charles, il

@Charles Ah sì, c'è la limitazione del set di caratteri, suppongo.
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);
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.