Meilleure façon d'inverser une chaîne

441

Je viens d'avoir à écrire une fonction de chaîne inversée en C # 2.0 (c'est-à-dire LINQ non disponible) et j'ai trouvé ceci:

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

Personnellement, je ne suis pas fou de la fonction et je suis convaincu qu'il existe une meilleure façon de le faire. Y a-t-il?

Gars
la source
51
Étonnamment délicat si vous voulez un soutien international approprié. Exemple: le croate / serbe a les lettres à deux caractères lj, nj etc. Le sens inverse de "ljudi" est "idulj", PAS "idujl". Je suis sûr que vous feriez bien pire en ce qui concerne l'arabe, le thaï, etc.
dbkk
Je me demande s'il est plus lent de concaténer une chaîne au lieu d'initialiser un tableau temporaire et de stocker les résultats dans cela, et enfin de convertir cela en chaîne?
The Muffin Man
2
Fil connexe beaucoup plus récent: inverser une chaîne avec des caractères d'accentuation?
Jeppe Stig Nielsen
5
Cette question pourrait être améliorée en définissant ce que vous entendez par «meilleur». Le plus rapide? Le plus lisible? La plus fiable dans divers cas marginaux (contrôles nuls, plusieurs langues, etc.)? La plus maintenable sur les versions de C # et .NET?
hypehuman

Réponses:

608
public static string Reverse( string s )
{
    char[] charArray = s.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}
PeteT
la source
16
sambo99: Il n'est pas nécessaire de mentionner unicode: les caractères en C # sont des caractères unicode, pas des octets. Xor peut être plus rapide, mais en plus d'être beaucoup moins lisible, c'est même ce que Array.Reverse () utilise en interne.
Nick Johnson
27
@Arachnid: En fait, les caractères en C # sont des unités de code UTF-16; il en faut deux pour représenter un caractère supplémentaire. Voir jaggersoft.com/csharp_standard/9.4.1.htm .
Bradley Grainger
4
Ouais sambo99 Je suppose que vous avez raison mais c'est un cas assez rare pour utiliser UTF-32. Et XOR n'est plus rapide que pour une très petite plage de valeurs, la bonne réponse serait d'implémenter différentes méthodes pour différentes longueurs, je suppose. Mais c'est clair et concis, ce qui est un avantage à mon avis.
PeteT
21
Les caractères de contrôle Unicode rendent cette méthode inutile pour les jeux de caractères non latins. Voir l'explication de Jon Skeet, en utilisant une marionnette à chaussettes: codeblog.jonskeet.uk/2009/11/02/… (1/4 en descendant), ou la vidéo: vimeo.com/7516539
Callum Rogers
20
J'espère que vous ne rencontrerez pas de substituts ou de combinaison de personnages.
dalle
183

Voici une solution qui inverse correctement la chaîne "Les Mise\u0301rables"comme "selbare\u0301siM seL". Cela devrait être rendu comme selbarésiM seL, non selbaŕesiM seL(notez la position de l'accent), comme le ferait la plupart des implémentations basées sur des unités de code ( Array.Reverse, etc.) ou même des points de code (inversant avec une attention particulière pour les paires de substitution).

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

(Et exemple de course en direct ici: https://ideone.com/DqAeMJ )

Il utilise simplement l' API .NET pour l'itération des grappes de graphèmes , qui existe depuis toujours, mais qui semble un peu "cachée".

R. Martinho Fernandes
la source
10
+1 L' un des très peu de réponses correctes, et beaucoup plus élégante et la preuve avenir que tous les autres, l' OMI
sehe
Cela échoue cependant pour certaines choses dépendantes des paramètres régionaux.
R. Martinho Fernandes
7
C'est amusant de voir comment la plupart des autres répondeurs essaient de se débarrasser de ce qui est autrement des approches incorrectes. Quelle représentativité.
G. Stoynev
2
Il est en fait beaucoup plus rapide d'instancier StringInfo (s), puis itérer à travers SubstringByTextElements (x, 1) et créer une nouvelle chaîne avec un StringBuilder.
2
C'est un peu étrange que vous ayez utilisé l'exemple de Jon Skeet qu'il a donné des années plus tôt codeblog.jonskeet.uk/2009/11/02/… Les Misérables (bien que Jon n'ait pas mentionné de solution, il a juste énuméré les problèmes). Heureusement que vous avez trouvé une solution. Peut-être que Jon skeet a inventé une machine à remonter le temps, est retourné en 2009 et a publié l'exemple de problème que vous avez utilisé dans votre solution.
barlop
126

Cela s'avère être une question étonnamment délicate.

Je recommanderais d'utiliser Array.Reverse pour la plupart des cas car il est codé en mode natif et il est très simple à maintenir et à comprendre.

Il semble surpasser StringBuilder dans tous les cas que j'ai testés.

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

Il existe une deuxième approche qui peut être plus rapide pour certaines longueurs de chaîne qui utilise 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);
    }

Remarque Si vous souhaitez prendre en charge le jeu de caractères complet Unicode UTF16, lisez ceci . Et utilisez plutôt l'implémentation là-bas. Il peut être encore optimisé en utilisant l'un des algorithmes ci-dessus et en parcourant la chaîne pour la nettoyer une fois les caractères inversés.

Voici une comparaison des performances entre la méthode StringBuilder, Array.Reverse et 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();
        }
    }
}

Voici les résultats:

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.

Il semble que Xor peut être plus rapide pour les chaînes courtes.

Sam Saffron
la source
2
Cela ne renvoie pas de chaîne - vous devez envelopper cela dans un appel à "nouvelle chaîne (...)"
Greg Beech
BTW .. Je viens de jeter un oeil à l'implémentation de Array.Reverse, et son fait naitively pour les caractères ... il devrait être beaucoup plus rapide que l'option StringBuilder.
Sam Saffron
Comme c'est gentil de votre part, Greg, d'arrêter Sambo d'arriver à une meilleure solution au lieu de le voter contre.
DOK
@ dok1 - ne le mentionnez pas :) @ sambo99 - maintenant je suis intrigué, je devrais sortir un profileur de code demain et y jeter un œil!
Greg Beech
9
Ces méthodes ne gèrent pas les chaînes contenant des caractères en dehors du plan multilingue de base, c'est-à-dire les caractères Unicode> = U + 10000 qui sont représentés avec deux caractères C #. J'ai publié une réponse qui gère correctement ces chaînes.
Bradley Grainger
52

Si vous pouvez utiliser LINQ (.NET Framework 3.5+), suivre un liner vous donnera un code court. N'oubliez pas d'ajouter using System.Linq;pour avoir accès à Enumerable.Reverse:

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

Remarques:

  • pas la version la plus rapide - selon Martin Niederl 5,7 fois plus lent que le choix le plus rapide ici.
  • ce code, comme de nombreuses autres options, ignore complètement toutes sortes de combinaisons multi-caractères, limitez donc l'utilisation aux devoirs et aux chaînes de devoirs qui ne contiennent pas de tels caractères. Voir une autre réponse dans cette question pour l'implémentation qui gère correctement ces combinaisons.
SGRao
la source
C'est environ 5,7 fois plus lent que la version la plus votée, donc je ne recommanderais pas d'utiliser cela!
Martin Niederl
2
Ce n'est pas la solution la plus rapide, mais utile en tant que doublure.
adrianmp
49

Si la chaîne contient des données Unicode (à proprement parler, des caractères non BMP), les autres méthodes qui ont été publiées le corrompent, car vous ne pouvez pas permuter l'ordre des unités de code de substitution hautes et basses lors de l'inversion de la chaîne. (Vous trouverez plus d'informations à ce sujet sur mon blog .)

L'exemple de code suivant inversera correctement une chaîne qui contient des caractères non BMP, par exemple, "\ 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);
}
Bradley Grainger
la source
29
En fait, les caractères en C # sont des unités de code UTF-16 16 bits; un caractère supplémentaire est encodé en utilisant deux d'entre eux, donc c'est nécessaire,
Bradley Grainger
14
Il semble que System.String devrait vraiment exposer une propriété HereBeDragons pour les chaînes qui contiennent des caractères supplémentaires Unicode.
Robert Rossney
4
@SebastianNegraszus: C'est exact: cette méthode inverse simplement les points de code dans la chaîne. Inverser les grappes de graphèmes serait probablement plus "utile" dans l'ensemble (mais à quoi sert "inverser une chaîne arbitraire en premier lieu?), Mais n'est pas facile à implémenter avec seulement les méthodes intégrées dans le .NET Framework.
Bradley Grainger
2
@Richard: Les règles de rupture des grappes de graphèmes sont un peu plus compliquées que la simple détection de la combinaison de points de code; voir la documentation sur Grapheme Cluster Boundaries dans UAX # 29 pour plus d'informations.
Bradley Grainger
1
Très bonne info! Est -ce que QUELQU'UN ont un test à défaut pour le test de Array.Reverse? Et par test, je veux dire un échantillon de chaîne et non un test unitaire entier ... Cela m'aiderait vraiment (et d'autres) à convaincre différentes personnes à propos de ce problème ..
Andrei Rînea
25

Ok, dans l'intérêt de "ne vous répétez pas", je vous propose la solution suivante:

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

Ma compréhension est que cette implémentation, disponible par défaut dans VB.NET, gère correctement les caractères Unicode.

richardtallent
la source
11
Cela ne gère que les substituts correctement. Il gâche la combinaison des marques: ideone.com/yikdqX .
R. Martinho Fernandes
17

Greg Beech a publié une unsafeoption qui est en effet aussi rapide que possible (c'est une inversion sur place); mais, comme il l'a indiqué dans sa réponse, c'est une idée complètement désastreuse .

Cela dit, je suis surpris qu'il y ait autant de consensus qui Array.Reversesoit la méthode la plus rapide. Il existe toujours une unsafeapproche qui renvoie une copie inversée d'une chaîne (pas de manigances d'inversion sur place) beaucoup plus rapidement que la Array.Reverseméthode pour les petites chaînes:

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

Voici quelques résultats de référence .

Vous pouvez voir que le gain de performances diminue, puis disparaît par rapport à la Array.Reverseméthode à mesure que les chaînes deviennent plus grandes. Pour les cordes petites à moyennes, cependant, il est difficile de battre cette méthode.

Dan Tao
la source
2
StackOverflow sur de grandes chaînes.
Raz Megrelidze
@rezomegreldize: Oui, ça va arriver;)
Dan Tao
15

La réponse facile et agréable utilise la méthode d'extension:

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

et voici la sortie:

string Answer = "12345".Inverse(); // = "54321"
Mehdi Khademloo
la source
Reverse()et ToArray()sont dans le mauvais ordre dans votre exemple de code.
Chris Walsh
À quoi sert le @?
user5389726598465
2
@ user5389726598465 Voir ce lien: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Parce que 'base' est un mot-clé en C #, il doit être préfixé par @ pour que le compilateur C # l'interprète comme un identifiant.
Dyndrilliac
14

Si vous voulez jouer à un jeu vraiment dangereux, c'est de loin le moyen le plus rapide qui soit (environ quatre fois plus rapide que la Array.Reverseméthode). C'est une marche arrière en place à l'aide de pointeurs.

Notez que je ne recommande vraiment jamais cela pour toute utilisation ( jetez un œil ici pour certaines raisons pour lesquelles vous ne devriez pas utiliser cette méthode ), mais il est juste intéressant de voir que cela peut être fait et que les chaînes ne sont pas vraiment immuables une fois que vous activez le code dangereux.

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
la source
Je suis presque sûr que cela retournera des résultats incorrects pour les chaînes utf16, cela pose vraiment des problèmes :)
Sam Saffron
Salut, vous devriez créer un lien vers cet article sur ce stackoverflow.com/questions/229346/… , comme je l'ai dit auparavant, cela pose vraiment des problèmes ...
Sam Saffron
Cela peut être complètement mauvais et mal avisé (comme vous le reconnaissez vous-même), mais il existe toujours un moyen performant d'inverser une chaîne en utilisant du unsafecode qui n'est pas mauvais et bat toujoursArray.Reverse dans de nombreux cas. Jetez un oeil à ma réponse.
Dan Tao
13

Jetez un œil à l'entrée wikipedia ici . Ils implémentent la méthode d'extension String.Reverse. Cela vous permet d'écrire du code comme ceci:

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

Ils utilisent également la combinaison ToCharArray / Reverse suggérée par d'autres réponses à cette question. Le code source ressemble à ceci:

public static string Reverse(this string input)
{
    char[] chars = input.ToCharArray();
    Array.Reverse(chars);
    return new String(chars);
}
Mike Thompson
la source
C'est merveilleux, sauf que les méthodes d'extension n'ont pas été introduites en c # 2.0.
Kobi
11

Tout d'abord, vous n'avez pas besoin d'appeler ToCharArraycar une chaîne peut déjà être indexée en tant que tableau de caractères, donc cela vous fera économiser une allocation.

L'optimisation suivante consiste à utiliser un StringBuilderpour éviter les allocations inutiles (car les chaînes sont immuables, leur concaténation fait une copie de la chaîne à chaque fois). Pour optimiser davantage cela, nous avons prédéfini la longueur du fichier StringBuilderafin qu'il n'ait pas besoin d'étendre son tampon.

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

Modifier: données de performance

J'ai testé cette fonction et la fonction à l'aide Array.Reversedu programme simple suivant, où se Reverse1trouve une fonction et Reverse2l'autre:

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

Il s'avère que pour les chaînes courtes, la Array.Reverseméthode est environ deux fois plus rapide que celle ci-dessus, et pour les chaînes plus longues, la différence est encore plus prononcée. Donc, étant donné que la Array.Reverseméthode est à la fois plus simple et plus rapide, je vous recommande de l'utiliser plutôt que celle-ci. Je laisse celui-ci ici juste pour montrer que ce n'est pas la façon dont vous devriez le faire (à ma grande surprise!)

Greg Beech
la source
Est-ce que le stockage de texte ne serait pas plus long dans une variable, ce qui donnerait un peu plus de vitesse lorsque vous le référencer via un objet?
David Robbins
10

Essayez d'utiliser Array.


public string Reverse(string str)
{
    char[] array = str.ToCharArray();
    Array.Reverse(array);
    return new string(array);
}
Mike Two
la source
C'est incroyablement rapide.
Michael Stum
Pourquoi le vote négatif? Sans le contester, mais je préfère apprendre de mes erreurs.
Mike Two
Ne parvient pas à gérer la combinaison de points de code, entre autres.
Mooing Duck
@MooingDuck - merci d'avoir expliqué, mais je ne sais pas ce que vous entendez par points de code. Pourriez-vous également développer «beaucoup d'autres choses».
Mike Two
@MooingDuck J'ai recherché des points de code. Oui. Vous avez raison. Il ne gère pas les points de code. Il est difficile de déterminer toutes les exigences pour une question aussi simple. Merci pour les commentaires
Mike Two
10
public static string Reverse(string input)
{
    return string.Concat(Enumerable.Reverse(input));
}

Bien sûr, vous pouvez étendre la classe de chaîne avec la méthode Reverse

public static class StringExtensions
{
    public static string Reverse(this string input)
    {
        return string.Concat(Enumerable.Reverse(input));
    }
}
Vlad Bezden
la source
Enumerable.Reverse(input)est égal àinput.Reverse()
fubo
8

"Best" peut dépendre de beaucoup de choses, mais voici quelques alternatives plus courtes ordonnées de rapide à lent:

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
la source
8

À partir de .NET Core 2.1, il existe une nouvelle façon d'inverser une chaîne à l'aide de la string.Createméthode.

Notez que cette solution ne gère pas correctement les caractères Unicode combinant etc., car "Les Mise \ u0301rables" serait converti en "selbarésiM seL". Les autres réponses pour une meilleure solution.

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

Cela copie essentiellement les caractères de inputdans une nouvelle chaîne et inverse la nouvelle chaîne en place.

Pourquoi est string.Createutile?

Lorsque nous créons une chaîne à partir d'un tableau existant, un nouveau tableau interne est alloué et les valeurs sont copiées. Sinon, il serait possible de muter une chaîne après sa création (dans un environnement sûr). Autrement dit, dans l'extrait de code suivant, nous devons allouer un tableau de longueur 10 deux fois, un comme tampon et un comme tableau interne de la chaîne.

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

string.Createnous permet essentiellement de manipuler le tableau interne lors de la création de la chaîne. Autrement dit, nous n'avons plus besoin d'un tampon et pouvons donc éviter d'allouer ce tableau de caractères.

Steve Gordon a écrit à ce sujet plus en détail ici . Il y a aussi un article sur MSDN .

Comment utiliser string.Create?

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

La méthode prend trois paramètres:

  1. La longueur de la chaîne à créer,
  2. les données que vous souhaitez utiliser pour créer dynamiquement la nouvelle chaîne,
  3. et un délégué qui crée la chaîne finale à partir des données, où le premier paramètre pointe vers le chartableau interne de la nouvelle chaîne et le second est les données (état) que vous avez transmises string.Create.

À l'intérieur du délégué, nous pouvons spécifier comment la nouvelle chaîne est créée à partir des données. Dans notre cas, nous copions simplement les caractères de la chaîne d'entrée dans ceux Spanutilisés par la nouvelle chaîne. Ensuite, nous inversons le Spanet donc la chaîne entière est inversée.

Repères

Pour comparer ma façon proposée d'inverser une chaîne avec la réponse acceptée, j'ai écrit deux repères en utilisant 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);
}

Voici les résultats sur ma machine:

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

Comme vous pouvez le voir, avec ReverseWithStringCreatenous n'allouons que la moitié de la mémoire utilisée par la ReverseWithArrayméthode.

Flogex
la source
Il est beaucoup plus rapide que Linq reverse
code4j
7

Ne vous embêtez pas avec une fonction, faites-le simplement en place. Remarque: La deuxième ligne générera une exception d'argument dans la fenêtre Exécution de certaines versions de VS.

string s = "Blah";
s = new string(s.ToCharArray().Reverse().ToArray()); 
BH
la source
1
Un gars a pris le temps de voter contre chaque réponse (la mienne incluse) sans expliquer pourquoi.
Marcel Valdez Orozco
Ce n'est pas vraiment en place, puisque vous créez unnew string
mbadawi23
5

Désolé pour le long post, mais cela pourrait être intéressant

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

Résultats:

  • ReverseUsingCharacterBuffer: 346ms
  • ReverseUsingArrayClass: 87ms
  • ReverseUsingStringBuilder: 824ms
  • ReverseUsingStack: 2086ms
  • ReverseUsingXOR: 319ms
aku
la source
J'ai ajouté une comparaison similaire dans mon article, c'est un wiki communautaire, vous devriez donc pouvoir le modifier. Les performances dépendent vraiment de la longueur de la chaîne ainsi que de l'algorithme, il serait intéressant de la représenter graphiquement. Je pense toujours que Array.Reverse sera le plus rapide dans tous les cas ...
Sam Saffron
"sera le plus rapide dans tous les cas" lorsque la fonction magique TrySZReverse (utilisée dans la mise en œuvre inverse) échoue, Array.Reverse se substitue à une mise en œuvre simple impliquant la boxe, donc ma méthode gagnera. Cependant, je ne sais pas quelle est la condition pour faire échouer TrySZReverse.
aku
Il s'avère que ce n'est pas le plus rapide dans tous les cas :), j'ai mis à jour mon message. Cela doit encore être testé avec unicode pour l'exactitude et la vitesse.
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();
    }
}

Production

Pour taille: 10

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

Pour taille: 100

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

Pour taille: 1000

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

Pour taille: 10000

Naive Ticks: 32
Efficient linear Ticks: 28
Recursive Ticks: 84808
DotNet Reverse Ticks: 33
Marcel Valdez Orozco
la source
1
Besoin de vérifier la chaîne vide Reverse(...). Sinon, bon travail.
Lara
5

Manière la plus simple:

string reversed = new string(text.Reverse().ToArray());
Shady Sirhan
la source
J'utilise la même phrase
VhsPiceros
4

Solution basée sur la pile.

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

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

        return new string(array);
    }

Ou

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

Dû soumettre un exemple récursif:

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
la source
1
la chaîne de longueur 0 n'est pas gérée
bohdan_trotsenko
Ce n'est pas utile.
user3613932
3

Que diriez-vous:

    private string Reverse(string stringToReverse)
    {
        char[] rev = stringToReverse.Reverse().ToArray();
        return new string(rev); 
    }
Zamir
la source
A les mêmes problèmes de point de code que les autres méthodes ci-dessus et fonctionnera beaucoup plus lentement que lors d'une ToCharArraypremière. L'énumérateur LINQ est également beaucoup plus lent que Array.Reverse().
Abel
3

J'ai créé un port C # à partir de Microsoft.VisualBasic.Strings . Je ne sais pas pourquoi ils gardent ces fonctions utiles (de VB) en dehors de System.String dans Framework, mais toujours sous Microsoft.VisualBasic. Même scénario pour les fonctions financières (par exemple 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
la source
+1, une belle addition! Je viens de l'essayer avec string s = "abo\u0327\u0307\u035d\U0001d166cd", qui contient la lettre osuivie de 3 combinaisons de signes diacritiques dans le BMP et une marque de combinaison (MUSICAL SYMBOL COMBINING STEM) du plan astral (non-BMP) et il les garde intacts. Mais la méthode est lente si ces caractères n'apparaissent qu'à la fin d'une longue chaîne, car elle doit parcourir deux fois l'ensemble du tableau.
Abel
3

Désolé de poster sur ce vieux fil. Je pratique du code pour une interview.

C'est ce que j'ai proposé pour C #. Ma première version avant refactoring était 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);
}

Contrairement à la Array.Reverseméthode ci-dessous, il apparaît plus rapidement avec 12 caractères ou moins dans la chaîne. Après 13 personnages, le Array.Reversecommence à s'accélérer, et il finit par dominer assez fortement sur la vitesse. Je voulais juste indiquer approximativement où la vitesse commence à changer.

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

    Array.Reverse(charA);

    return new String(charA);
}

À 100 caractères dans la chaîne, c'est plus rapide que ma version x 4. Cependant, si je savais que les chaînes auraient toujours moins de 13 caractères, j'utiliserais celle que j'ai faite.

Les tests ont été effectués avec Stopwatchet 5000000 itérations. De plus, je ne sais pas si ma version gère les substituts ou les situations de caractères combinées avec l' Unicodeencodage.

Jason Ausborn
la source
2

"Meilleure façon" dépend de ce qui est le plus important pour vous dans votre situation, performance, élégance, maintenabilité, etc.

Quoi qu'il en soit, voici une approche utilisant Array.

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

string reversed = new string(charArray);
Cendre
la source
2

Si cela est arrivé dans une interview et qu'on vous a dit que vous ne pouviez pas utiliser Array, l'inverse, je pense que cela pourrait être l'un des plus rapides. Il ne crée pas de nouvelles chaînes et itère seulement plus de la moitié du tableau (c'est-à-dire O (n / 2) itérations)

    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
la source
2
Je suis quasiment certain que l'appel stringToReverse.ToCharArray () produira un temps d'exécution O (N).
Marcel Valdez Orozco
En notation Big-O , le facteur ne dépendant pas x, ou dans votre cas n, n'est pas utilisé. Votre algorithme a des performances f(x) = x + ½x + C, où C est une constante. Puisque les deux Cet le facteur ne dépendent pas x, votre algorithme l'est O(x). Cela ne signifie pas qu'il ne sera pas plus rapide pour toute entrée de longueur x, mais ses performances dépendent linéairement de la longueur d'entrée. Pour répondre à @MarcelValdezOrozco, oui, il l'est aussi O(n), bien qu'il copie par morceaux de 16 octets pour améliorer la vitesse (il n'utilise pas de ligne droite memcpysur la longueur totale).
Abel
2

Si vous avez une chaîne qui ne contient que des caractères ASCII, vous pouvez utiliser cette méthode.

    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
la source
2

Tout d'abord, ce que vous devez comprendre, c'est que str + = redimensionnera la mémoire de votre chaîne pour faire de la place pour 1 caractère supplémentaire. C'est très bien, mais si vous avez, disons, un livre de 1000 pages que vous souhaitez inverser, cela prendra très longtemps à exécuter.

La solution que certaines personnes pourraient suggérer est d'utiliser StringBuilder. Ce que fait le générateur de chaînes lorsque vous effectuez un + =, c'est qu'il alloue des morceaux de mémoire beaucoup plus grands pour contenir le nouveau caractère de sorte qu'il n'a pas besoin de faire une réallocation chaque fois que vous ajoutez un caractère.

Si vous voulez vraiment une solution rapide et minimale, je vous suggère ce qui suit:

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

Dans cette solution, il y a une allocation de mémoire initiale lorsque le char [] est initialisé et une allocation lorsque le constructeur de chaîne crée la chaîne à partir du tableau de char.

Sur mon système, j'ai effectué un test pour vous qui inverse une chaîne de 2 750 000 caractères. Voici les résultats de 10 exécutions:

StringBuilder: 190K - 200K ticks

Tableau des chars: 130K - 160K ticks

J'ai également exécuté un test pour String + = normal, mais je l'ai abandonné après 10 minutes sans sortie.

Cependant, j'ai également remarqué que pour les petites chaînes, StringBuilder est plus rapide, vous devrez donc décider de l'implémentation en fonction de l'entrée.

À votre santé

Reasurria
la source
ne fonctionne pas pour😀Les Misérables
Charles
@Charles Ah oui il y a la limitation de jeu de caractères je suppose.
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
la source
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
la source