Найкращий спосіб повернути рядок


440

Я просто повинен був написати функцію зворотного рядка в C # 2.0 (тобто LINQ недоступний) і придумав це:

public string Reverse(string text)
{
    char[] cArray = text.ToCharArray();
    string reverse = String.Empty;
    for (int i = cArray.Length - 1; i > -1; i--)
    {
        reverse += cArray[i];
    }
    return reverse;
}

Особисто я не божевільний щодо функції та переконаний, що є кращий спосіб це зробити. Є там?


51
Дивно складно, якщо ви хочете належної міжнародної підтримки. Приклад: у хорватських / сербських є двозначні букви lj, nj тощо. Належним зворотом слова "люди" є "idulj", а не "idujl". Я впевнений, що у вас буде набагато гірше, якщо мова йде про арабську, тайську тощо.
dbkk

Цікаво, чи повільніше укладати рядок, а не ініціалізувати тимчасовий масив і зберегти в ньому результати, а потім остаточно перетворити його в рядок?
Людина-булочка


5
Це питання можна було б покращити, визначивши, що ви маєте на увазі під «найкращим». Найшвидший? Найбільш читабельні? Найбільш надійний у різних кращих випадках (нульові перевірки, кілька мов тощо)? Найбільш рентабельний у версіях C # та .NET?
гіпегуман

Відповіді:


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

16
sambo99: Не потрібно згадувати про unicode: символи в C # - це символи unicode, а не байти. Xor може бути швидшим, але крім того, що він набагато менш читабельний, це може бути навіть те, що Array.Reverse () використовує внутрішньо.
Нік Джонсон

27
@Arachnid: Насправді символи в C # - це кодові одиниці UTF-16; потрібно два з них, щоб представити додатковий характер. Див. Jaggersoft.com/csharp_standard/9.4.1.htm .
Бредлі Грінґер

4
Так, sambo99 Я вважаю, що ви маєте рацію, але UTF-32 є досить рідкісним випадком. І XOR швидше лише за дуже малий діапазон значень, правильною відповіддю було б впровадити різні методи різної довжини, я думаю. Але це чітко і стисло, що на мою думку є вигодою.
PeteT

21
Контрольні символи Unicode роблять цей метод марним для наборів символів, які не є латинськими. Дивіться пояснення Джона Скіта, використовуючи ляльку-шкарпетку: codeblog.jonskeet.uk/2009/11/02/… (1/4 вниз), або відео: vimeo.com/7516539
Callum Rogers

20
Сподіваємось, ви не зіткнетесь із жодними сурогатами чи поєднаннями символів.
Далле

183

Тут рішення, яке правильно обертає рядок "Les Mise\u0301rables"як "selbare\u0301siM seL". Це має відображатись так само selbarésiM seL, а не selbaŕesiM seL(відзначати позицію акценту), як це було б результатом більшості реалізацій на основі одиниць коду ( Array.Reverseтощо) або навіть кодових точок (обертання з особливою обережністю для сурогатних пар).

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

public static class Test
{
    private static IEnumerable<string> GraphemeClusters(this string s) {
        var enumerator = StringInfo.GetTextElementEnumerator(s);
        while(enumerator.MoveNext()) {
            yield return (string)enumerator.Current;
        }
    }
    private static string ReverseGraphemeClusters(this string s) {
        return string.Join("", s.GraphemeClusters().Reverse().ToArray());
    }

    public static void Main()
    {
        var s = "Les Mise\u0301rables";
        var r = s.ReverseGraphemeClusters();
        Console.WriteLine(r);
    }
}

(І живий приклад тут: https://ideone.com/DqAeMJ )

Він просто використовує API .NET для ітерації кластерних графем , який існує з усіх часів, але, здається, трохи "прихований" від перегляду.


10
+1 Один з небагатьох правильних відповідей та набагато більш елегантний та майбутній доказ, ніж будь-який із інших, IMO
1313

Однак це не вдається для деяких матеріалів, що залежать від місцевості.
Р. Мартіньо Фернандес

7
Смішно, як більшість інших відповідачів намагаються відгородити повідомлення, що є інакше невірними підходами. Як представницький.
Г. Стойнев

2
Насправді значно швидше інстанціювати StringInfo (ів), потім повторити через SubstringByTextElements (x, 1) та створити нову рядок з StringBuilder.

2
Дещо дивно, що ви використовували приклад Джона Скіта, який він давав роками раніше codeblog.jonskeet.uk/2009/11/02/… Les Misérables (хоча Джон не згадував про рішення, він просто перераховував проблеми). Добре, що ви придумали рішення. Можливо, Джон Скейт винайшов машину часу, повернувся до 2009 року і розмістив приклад проблеми, який ви використовували у своєму рішенні.
барлоп

126

Це виявляється напрочуд складним питанням.

Я б рекомендував використовувати Array.Reverse для більшості випадків, оскільки він кодується в основному, і його дуже просто підтримувати та розуміти.

Здається, перевершує StringBuilder у всіх перевірених нами випадках.

public string Reverse(string text)
{
   if (text == null) return null;

   // this was posted by petebob as well 
   char[] array = text.ToCharArray();
   Array.Reverse(array);
   return new String(array);
}

Є другий підхід, який може бути швидшим для певної довжини рядків, який використовує Xor .

    public static string ReverseXor(string s)
    {
        if (s == null) return null;
        char[] charArray = s.ToCharArray();
        int len = s.Length - 1;

        for (int i = 0; i < len; i++, len--)
        {
            charArray[i] ^= charArray[len];
            charArray[len] ^= charArray[i];
            charArray[i] ^= charArray[len];
        }

        return new string(charArray);
    }

Примітка. Якщо ви хочете підтримувати повну діаграму Unicode UTF16, прочитайте це . І замість цього скористайтеся реалізацією. Його можна додатково оптимізувати, використовуючи один з вищезазначених алгоритмів та пробігаючи рядок, щоб очистити його після того, як символи будуть перетворені.

Ось порівняння продуктивності між методом StringBuilder, Array.Reverse та Xor.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication4
{
    class Program
    {
        delegate string StringDelegate(string s);

        static void Benchmark(string description, StringDelegate d, int times, string text)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int j = 0; j < times; j++)
            {
                d(text);
            }
            sw.Stop();
            Console.WriteLine("{0} Ticks {1} : called {2} times.", sw.ElapsedTicks, description, times);
        }

        public static string ReverseXor(string s)
        {
            char[] charArray = s.ToCharArray();
            int len = s.Length - 1;

            for (int i = 0; i < len; i++, len--)
            {
                charArray[i] ^= charArray[len];
                charArray[len] ^= charArray[i];
                charArray[i] ^= charArray[len];
            }

            return new string(charArray);
        }

        public static string ReverseSB(string text)
        {
            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }
            return builder.ToString();
        }

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

        public static string StringOfLength(int length)
        {
            Random random = new Random();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                sb.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
            }
            return sb.ToString();
        }

        static void Main(string[] args)
        {

            int[] lengths = new int[] {1,10,15,25,50,75,100,1000,100000};

            foreach (int l in lengths)
            {
                int iterations = 10000;
                string text = StringOfLength(l);
                Benchmark(String.Format("String Builder (Length: {0})", l), ReverseSB, iterations, text);
                Benchmark(String.Format("Array.Reverse (Length: {0})", l), ReverseArray, iterations, text);
                Benchmark(String.Format("Xor (Length: {0})", l), ReverseXor, iterations, text);

                Console.WriteLine();    
            }

            Console.Read();
        }
    }
}

Ось результати:

26251 Ticks String Builder (Length: 1) : called 10000 times.
33373 Ticks Array.Reverse (Length: 1) : called 10000 times.
20162 Ticks Xor (Length: 1) : called 10000 times.

51321 Ticks String Builder (Length: 10) : called 10000 times.
37105 Ticks Array.Reverse (Length: 10) : called 10000 times.
23974 Ticks Xor (Length: 10) : called 10000 times.

66570 Ticks String Builder (Length: 15) : called 10000 times.
26027 Ticks Array.Reverse (Length: 15) : called 10000 times.
24017 Ticks Xor (Length: 15) : called 10000 times.

101609 Ticks String Builder (Length: 25) : called 10000 times.
28472 Ticks Array.Reverse (Length: 25) : called 10000 times.
35355 Ticks Xor (Length: 25) : called 10000 times.

161601 Ticks String Builder (Length: 50) : called 10000 times.
35839 Ticks Array.Reverse (Length: 50) : called 10000 times.
51185 Ticks Xor (Length: 50) : called 10000 times.

230898 Ticks String Builder (Length: 75) : called 10000 times.
40628 Ticks Array.Reverse (Length: 75) : called 10000 times.
78906 Ticks Xor (Length: 75) : called 10000 times.

312017 Ticks String Builder (Length: 100) : called 10000 times.
52225 Ticks Array.Reverse (Length: 100) : called 10000 times.
110195 Ticks Xor (Length: 100) : called 10000 times.

2970691 Ticks String Builder (Length: 1000) : called 10000 times.
292094 Ticks Array.Reverse (Length: 1000) : called 10000 times.
846585 Ticks Xor (Length: 1000) : called 10000 times.

305564115 Ticks String Builder (Length: 100000) : called 10000 times.
74884495 Ticks Array.Reverse (Length: 100000) : called 10000 times.
125409674 Ticks Xor (Length: 100000) : called 10000 times.

Здається, що Xor може бути швидшим для коротких струн.


2
Це не повертає рядок - вам потрібно зафіксувати це у виклику до "нової струни (...)"
Грег Бук

BTW .. Я просто подивився на реалізацію Array.Reverse, і це було зроблено наївно для символів ... це повинно бути набагато швидше, ніж варіант StringBuilder.
Сем Шафран

Як із вас, Грег, зупинити Самбо, щоб досягти кращого рішення замість того, щоб його голосувати.
DOK

@ dok1 - не згадуй про це :) @ sambo99 - тепер мене заінтригує, доведеться завтра вибивати кодек-профілер і подивитись!
Грег Бук

9
Ці методи не обробляють рядки, що містять символи за межами базової багатомовної площини, тобто символи Unicode> = U + 10000, які представлені двома символами C #. Я опублікував відповідь, що правильно обробляє такі рядки.
Бредлі Грінґер

52

Якщо ви можете використовувати LINQ (.NET Framework 3.5+), ніж наступний один лайнер дасть вам короткий код. Не забудьте додати, using System.Linq;щоб мати доступ до Enumerable.Reverse:

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

Примітки:

  • не найшвидша версія - за словами Мартіна Нідерла в 5,7 рази повільніше, ніж найшвидший вибір тут.
  • цей код, як і багато інших параметрів, повністю ігнорує всілякі комбінації з декількома символами, тому обмежте використання домашніх завдань та рядків, які не містять таких символів. Дивіться іншу відповідь у цьому питанні щодо реалізації, яка правильно обробляє такі комбінації.

Це приблизно в 5,7 рази повільніше, ніж найбільш оновлена ​​версія, тому я б не рекомендував використовувати це!
Мартін Нідерл

2
Не найшвидше рішення, але корисне як однолінійний.
adrianmp

49

Якщо рядок містить дані Unicode (строго кажучи, символи, що не належать до BMP), інші опубліковані методи пошкодять її, тому що ви не можете змінити порядок вищих і низьких сурогатних кодних кодів під час повернення рядка. (Більше інформації про це можна знайти в моєму блозі .)

Наведений нижче зразок коду буде правильно обертати рядок, що містить символи, що не належать до BMP, наприклад, "\ U00010380 \ U00010381" (Ugaritic Letter Alpa, Ugaritic Letter Beta).

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

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

    return new string(output);
}

29
Власне, символи в C # - це 16-бітні кодові UTF-16; Додатковий символ кодується за допомогою двох з них, тому це необхідно,
Бредлі Грінґер

14
Схоже, System.String дійсно повинен викрити властивість HereBeDragons для рядків, що містять додаткові символи Unicode.
Роберт Россні

4
@SebastianNegraszus: Це правильно: цей метод просто повертає кодові точки в рядку. Звертати кластери графем , ймовірно , буде більш «корисним» в цілому (але що «використання» реверсування довільній рядки в першу чергу?), Але не так легко реалізувати з допомогою тільки вбудованих методів в .NET Framework.
Бредлі Грінґер

2
@ Richard: Правила розбиття кластерів графем трохи складніші, ніж просто виявлення комбінування точок коду; Додаткову інформацію див. у документації щодо меж кластерного графему в UAX №29.
Bradley Grainger

1
Дуже гарна інформація! Є чи НІКОМУ є незадовільний тест для випробування Array.Reverse? І під тестом я маю на увазі зразок рядка, а не цілий одиничний тест ... Це дійсно допомогло б мені (та іншим) переконати різних людей у ​​цьому питанні ..
Андрій Ронеа

25

Гаразд, в інтересах "не повторюй себе" я пропоную таке рішення:

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

Я розумію, що ця реалізація, доступна за замовчуванням у VB.NET, належним чином обробляє символи Unicode.


11
Це лише належним чином поводиться з сурогатами. Це псує комбінуючі позначки: ideone.com/yikdqX .
Р. Мартіньо Фернандес

17

Грег Бук опублікував unsafeваріант, який дійсно є настільки ж швидким, як він отримує (це місце натомість); але, як він зазначив у своїй відповіді, це абсолютно згубна ідея .

Однак я здивований, що існує стільки консенсусу, що Array.Reverseце найшвидший метод. Досі існує unsafeпідхід, який повертає обернену копію рядка (немає на місці шенаніганів), ніж Array.Reverseметод для невеликих рядків:

public static unsafe string Reverse(string text)
{
    int len = text.Length;

    // Why allocate a char[] array on the heap when you won't use it
    // outside of this method? Use the stack.
    char* reversed = stackalloc char[len];

    // Avoid bounds-checking performance penalties.
    fixed (char* str = text)
    {
        int i = 0;
        int j = i + len - 1;
        while (i < len)
        {
            reversed[i++] = str[j--];
        }
    }

    // Need to use this overload for the System.String constructor
    // as providing just the char* pointer could result in garbage
    // at the end of the string (no guarantee of null terminator).
    return new string(reversed, 0, len);
}

Ось деякі результати орієнтиру .

Ви можете бачити, що посилення продуктивності скорочується, а потім зникає проти Array.Reverseметоду в міру збільшення рядків. Для невеликих і середніх струн, однак, важко перемогти цей метод.


2
StackOverflow на великих струнах.
Raz Megrelidze

@rezomegreldize: Так, це станеться;)
Дан Дао

15

Легка і приємна відповідь - це метод розширення:

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

і ось результат:

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

Reverse()і ToArray()в неправильному порядку у вашому зразку коду.
Кріс Уолш

Якій цілі служить @?
користувач5389726598465

2
@ user5389726598465 Перейдіть за цим посиланням: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… Оскільки "база" - це ключове слово в C #, воно повинно бути встановлено з @ для компілятора C #, щоб інтерпретувати його як ідентифікатор.
Діндріліак

14

Якщо ви хочете пограти в дійсно небезпечну гру, то це, безумовно, найшвидший спосіб (приблизно в чотири рази швидше, ніж Array.Reverseметод). Це реверс на місці за допомогою покажчиків.

Зауважте, що я дійсно не рекомендую це ні для якого використання ( будь ласка, подивіться тут з деяких причин, чому ви не повинні використовувати цей метод ), але цікаво лише побачити, що це можна зробити, і що струни насправді не змінюються як тільки ви увімкнете небезпечний код.

public static unsafe string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    fixed (char* pText = text)
    {
        char* pStart = pText;
        char* pEnd = pText + text.Length - 1;
        for (int i = text.Length / 2; i >= 0; i--)
        {
            char temp = *pStart;
            *pStart++ = *pEnd;
            *pEnd-- = temp;
        }

        return text;
    }
}

Я впевнений, що це призведе до неправильних результатів для рядків utf16, це справді задає проблеми :)
Сем Саффрон

Привіт, ви повинні посилатися на цю публікацію на цьому stackoverflow.com/questions/229346/… , як я вже говорив, перш ніж це справді задає проблеми ...
Сем Шафран

Це може бути абсолютно злим і необдуманим (як ви самі визнаєте), але все ж є високоефективний спосіб повернути рядок за допомогою unsafeкоду, який не є злим і все ще б'є Array.Reverseв багатьох випадках. Погляньте на мою відповідь.
Дан Дао

13

Подивіться на запис у Вікіпедії тут . Вони реалізують метод розширення String.Reverse. Це дозволяє писати такий код:

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

Вони також використовують комбінацію ToCharArray / Reverse, яку підказують інші відповіді на це питання. Вихідний код виглядає так:

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

Це чудово, окрім методів розширення не було впроваджено в c # 2.0.
Кобі

11

По-перше, вам не потрібно дзвонити, ToCharArrayоскільки рядок вже може бути індексовано як масив char, так що це заощадить вам розподіл.

Наступною оптимізацією є використання a StringBuilderдля запобігання зайвих виділень (оскільки рядки незмінні, їх об'єднання кожного разу робить копію рядка). Для подальшої оптимізації цього заздалегідь задаємо довжину, StringBuilderтому не потрібно буде розширювати її буфер.

public string Reverse(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return text;
    }

    StringBuilder builder = new StringBuilder(text.Length);
    for (int i = text.Length - 1; i >= 0; i--)
    {
        builder.Append(text[i]);
    }

    return builder.ToString();
}

Редагувати: дані про ефективність

Я перевірив цю функцію та функцію за Array.Reverseдопомогою наступної простої програми, де Reverse1одна функція, а Reverse2інша:

static void Main(string[] args)
{
    var text = "abcdefghijklmnopqrstuvwxyz";

    // pre-jit
    text = Reverse1(text); 
    text = Reverse2(text);

    // test
    var timer1 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse1(text);
    }

    timer1.Stop();
    Console.WriteLine("First: {0}", timer1.ElapsedMilliseconds);

    var timer2 = Stopwatch.StartNew();
    for (var i = 0; i < 10000000; i++)
    {
        text = Reverse2(text);
    }

    timer2.Stop();
    Console.WriteLine("Second: {0}", timer2.ElapsedMilliseconds);

    Console.ReadLine();
}

Виявляється, що для коротких рядків Array.Reverseметод є приблизно вдвічі швидшим, ніж вище, а для довших рядків різниця ще більше виражена. Отже, враховуючи, що Array.Reverseметод є і простішим, і швидшим, я б рекомендував вам використовувати його, а не цей. Я залишаю цю угору просто для того, щоб показати, що це не так, як ви повинні це зробити (на мій подив!)


Не зберігає текст.Задовження у змінній дасть трохи більше швидкості, оскільки ви посилаєтесь на це через об’єкт?
Девід Роббінс

10

Спробуйте використовувати Array.Reverse


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

Це неймовірно швидко.
Майкл Штум

Чому голосування "за"? Не сперечаючись, але я краще навчуся на своїх помилках.
Майк другий

Не вдається обробити комбінування кодових точок серед багатьох інших речей.
Mooing Duck

@MooingDuck - дякую за пояснення, але я не знаю, що ви маєте на увазі під кодовими пунктами. Також ви могли б детальніше розповісти про "багато іншого".
Майк другий

@MooingDuck Я шукав кодові очки. Так. Ви праві. Він не обробляє кодові точки. Важко визначити всі вимоги до такого простого вигляду питання. Дякую за відгук
Mike Two

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

Звичайно, ви можете розширити клас рядків методом Reverse

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

Enumerable.Reverse(input)дорівнюєinput.Reverse()
fubo

8

"Найкраще" може залежати від багатьох речей, але ось ще кілька коротких альтернатив, упорядкованих від швидкого до повільного:

string s = "z̽a̎l͘g̈o̓😀😆", pattern = @"(?s).(?<=(?:.(?=.*$(?<=((\P{M}\p{C}?\p{M}*)\1?))))*)";

string s1 = string.Concat(s.Reverse());                          // "☐😀☐̓ög͘l̎a̽z"  👎

string s2 = Microsoft.VisualBasic.Strings.StrReverse(s);         // "😆😀o̓g̈l͘a̎̽z"  👌

string s3 = string.Concat(StringInfo.ParseCombiningCharacters(s).Reverse()
    .Select(i => StringInfo.GetNextTextElement(s, i)));          // "😆😀o̓g̈l͘a̎z̽"  👍

string s4 = Regex.Replace(s, pattern, "$2").Remove(s.Length);    // "😆😀o̓g̈l͘a̎z̽"  👍

8

Починаючи з .NET Core 2.1 існує новий спосіб повернути рядок за допомогою string.Create методу.

Зауважте, що це рішення не обробляє Unicode об'єднання символів тощо правильно, оскільки "Les Mise \ u0301rables" буде перетворено на "selbarésiM seL". В інші відповіді для кращого вирішення.

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

Це по суті копіює символи inputнового рядка і повертає нову рядок на місце.

Чому string.Createкорисний?

Коли ми створюємо рядок із існуючого масиву, виділяється новий внутрішній масив і копіюються значення. В іншому випадку можна було б мутувати рядок після його створення (у безпечному середовищі). Тобто, у наступному фрагменті ми повинні виділити масив довжиною 10 вдвічі, один як буфер, а один як внутрішній масив рядка.

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

string.Createпо суті дозволяє нам маніпулювати внутрішнім масивом під час створення рядка. Це означає, що нам більше не потрібен буфер, і тому ми можемо уникати виділення цього масиву char.

Стів Гордон детальніше написав про це тут . Також є стаття про MSDN .

Як користуватися string.Create?

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

Метод приймає три параметри:

  1. Довжина рядка для створення,
  2. дані, які ви хочете використовувати для динамічного створення нового рядка,
  3. і делегат, який створює заключну рядок з даних, де перший параметр вказує на внутрішній charмасив нової рядки, а другий - дані (стан), до яких ви передали string.Create.

Всередині делегата ми можемо вказати, як створюється нова рядок із даних. У нашому випадку ми просто копіюємо символи вхідного рядка у Spanвикористаний новий рядок. Потім перевертаємоSpan і, отже, весь рядок .

Орієнтири

Для порівняння запропонованого способу перевороту рядка з прийнятою відповіддю я написав два орієнтири за допомогою BenchmarkDotNet.

public class StringExtensions
{
    public static string ReverseWithArray(string input)
    {
        var charArray = input.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }

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

[MemoryDiagnoser]
public class StringReverseBenchmarks
{
    private string input;

    [Params(10, 100, 1000)]
    public int InputLength { get; set; }


    [GlobalSetup]
    public void SetInput()
    {
        // Creates a random string of the given length
        this.input = RandomStringGenerator.GetString(InputLength);
    }

    [Benchmark(Baseline = true)]
    public string WithReverseArray() => StringExtensions.ReverseWithArray(input);

    [Benchmark]
    public string WithStringCreate() => StringExtensions.ReverseWithStringCreate(input);
}

Ось результати на моїй машині:

| Method           | InputLength |         Mean |      Error |    StdDev |  Gen 0 | Allocated |
| ---------------- | ----------- | -----------: | ---------: | --------: | -----: | --------: |
| WithReverseArray | 10          |    45.464 ns |  0.4836 ns | 0.4524 ns | 0.0610 |      96 B |
| WithStringCreate | 10          |    39.749 ns |  0.3206 ns | 0.2842 ns | 0.0305 |      48 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 100         |   175.162 ns |  2.8766 ns | 2.2458 ns | 0.2897 |     456 B |
| WithStringCreate | 100         |   125.284 ns |  2.4657 ns | 2.0590 ns | 0.1473 |     232 B |
|                  |             |              |            |           |        |           |
| WithReverseArray | 1000        | 1,523.544 ns |  9.8808 ns | 8.7591 ns | 2.5768 |    4056 B |
| WithStringCreate | 1000        | 1,078.957 ns | 10.2948 ns | 9.6298 ns | 1.2894 |    2032 B |

Як бачите, ReverseWithStringCreateми виділяємо лише половину пам'яті, використовуваної ReverseWithArrayметодом.


Це набагато швидше, ніж зворотний
зв'язок

7

Не переймайтесь функцією, просто виконайте це на місці. Примітка. Другий рядок викине виключення аргументу у Негайне вікно деяких версій VS.

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

1
Якийсь хлопець знайшов час, щоб схилити кожну відповідь (включаючи мою), не пояснюючи чому.
Марсель Вальдес Ороско

Це насправді не на місці, оскільки ви створюєтеnew string
mbadawi23

5

Вибачте за довге повідомлення, але це може бути цікавим

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        public static string ReverseUsingArrayClass(string text)
        {
            char[] chars = text.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }

        public static string ReverseUsingCharacterBuffer(string text)
        {
            char[] charArray = new char[text.Length];
            int inputStrLength = text.Length - 1;
            for (int idx = 0; idx <= inputStrLength; idx++) 
            {
                charArray[idx] = text[inputStrLength - idx];                
            }
            return new string(charArray);
        }

        public static string ReverseUsingStringBuilder(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return text;
            }

            StringBuilder builder = new StringBuilder(text.Length);
            for (int i = text.Length - 1; i >= 0; i--)
            {
                builder.Append(text[i]);
            }

            return builder.ToString();
        }

        private static string ReverseUsingStack(string input)
        {
            Stack<char> resultStack = new Stack<char>();
            foreach (char c in input)
            {
                resultStack.Push(c);
            }

            StringBuilder sb = new StringBuilder();
            while (resultStack.Count > 0)
            {
                sb.Append(resultStack.Pop());
            }
            return sb.ToString();
        }

        public static string ReverseUsingXOR(string text)
        {
            char[] charArray = text.ToCharArray();
            int length = text.Length - 1;
            for (int i = 0; i < length; i++, length--)
            {
                charArray[i] ^= charArray[length];
                charArray[length] ^= charArray[i];
                charArray[i] ^= charArray[length];
            }

            return new string(charArray);
        }


        static void Main(string[] args)
        {
            string testString = string.Join(";", new string[] {
                new string('a', 100), 
                new string('b', 101), 
                new string('c', 102), 
                new string('d', 103),                                                                   
            });
            int cycleCount = 100000;

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingCharacterBuffer(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingCharacterBuffer: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingArrayClass(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingArrayClass: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStringBuilder(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStringBuilder: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingStack(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingStack: " + stopwatch.ElapsedMilliseconds + "ms");

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < cycleCount; i++) 
            {
                ReverseUsingXOR(testString);
            }
            stopwatch.Stop();
            Console.WriteLine("ReverseUsingXOR: " + stopwatch.ElapsedMilliseconds + "ms");            
        }
    }
}

Результати:

  • ReverseUsingCharacterBuffer: 346 мс
  • ReverseUsingArrayClass: 87 мс
  • ReverseUsingStringBuilder: 824ms
  • ReverseUsingStack: 2086ms
  • Зворотне використанняXOR: 319 мс

Я додав подібне порівняння у своєму дописі, його вікі спільноти, тож ви зможете редагувати. Продуктивність дійсно залежить від довжини рядка, а також алгоритму, було б цікаво його графікувати. Я все ще думаю, що Array.Reverse буде найшвидшим у всіх випадках ...
Сем Шафрон

"буде найшвидшим у всіх випадках", коли магічна функція TrySZReverse (використовується в реверсній реалізації) не вдається, Array.Reverse відступає до простої реалізації, що включає бокс, тому мій метод виграє. Однак я не знаю, що є умовою, щоб TrySZReverse вийшов з ладу.
Аку

Виявляється, це не найшвидше у всіх випадках :), я оновив свій пост. Це все ще потрібно перевірити Unicode як на правильність, так і на швидкість.
Сем Шафран

5
public string Reverse(string input)
{
    char[] output = new char[input.Length];

    int forwards = 0;
    int backwards = input.Length - 1;

    do
    {
        output[forwards] = input[backwards];
        output[backwards] = input[forwards];
    }while(++forwards <= --backwards);

    return new String(output);
}

public string DotNetReverse(string input)
{
    char[] toReverse = input.ToCharArray();
    Array.Reverse(toReverse);
    return new String(toReverse);
}

public string NaiveReverse(string input)
{
    char[] outputArray = new char[input.Length];
    for (int i = 0; i < input.Length; i++)
    {
        outputArray[i] = input[input.Length - 1 - i];
    }

    return new String(outputArray);
}    

public string RecursiveReverse(string input)
{
    return RecursiveReverseHelper(input, 0, input.Length - 1);
}

public string RecursiveReverseHelper(string input, int startIndex , int endIndex)
{
    if (startIndex == endIndex)
    {
        return "" + input[startIndex];
    }

    if (endIndex - startIndex == 1)
    {
        return "" + input[endIndex] + input[startIndex];
    }

    return input[endIndex] + RecursiveReverseHelper(input, startIndex + 1, endIndex - 1) + input[startIndex];
}


void Main()
{
    int[] sizes = new int[] { 10, 100, 1000, 10000 };
    for(int sizeIndex = 0; sizeIndex < sizes.Length; sizeIndex++)
    {
        string holaMundo  = "";
        for(int i = 0; i < sizes[sizeIndex]; i+= 5)
        {   
            holaMundo += "ABCDE";
        }

        string.Format("\n**** For size: {0} ****\n", sizes[sizeIndex]).Dump();

        string odnuMaloh = DotNetReverse(holaMundo);

        var stopWatch = Stopwatch.StartNew();
        string result = NaiveReverse(holaMundo);
        ("Naive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = Reverse(holaMundo);
        ("Efficient linear Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = RecursiveReverse(holaMundo);
        ("Recursive Ticks: " + stopWatch.ElapsedTicks).Dump();

        stopWatch.Restart();
        result = DotNetReverse(holaMundo);
        ("DotNet Reverse Ticks: " + stopWatch.ElapsedTicks).Dump();
    }
}

Вихідні дані

Для розміру: 10

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

Для розміру: 100

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

Для розміру: 1000

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

Для розміру: 10000

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

1
Потрібно перевірити наявність порожнього рядка в Reverse(...). Інакше хороша робота.
Лара

5

Найпростіший спосіб:

string reversed = new string(text.Reverse().ToArray());

Я використовую те саме речення
VhsPiceros

4

Рішення на основі стека.

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

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

        return new string(array);
    }

Або

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

4

Довелося подати рекурсивний приклад:

private static string Reverse(string str)
{
    if (str.IsNullOrEmpty(str) || str.Length == 1)
        return str;
    else
        return str[str.Length - 1] + Reverse(str.Substring(0, str.Length - 1));
}

1
рядок довжиною 0 не обробляється
bohdan_trotsenko

Це не корисно.
користувач3613932

3

Як щодо:

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

Має ті ж проблеми з кодовою точкою, що й інші методи, наведені вище, і працюватиме набагато повільніше, ніж при ToCharArrayпершому виконанні. Перелік LINQ також набагато повільніше, ніж Array.Reverse().
Абель

3

Я зробив порт C # від Microsoft.VisualBasic.Strings . Я не впевнений, чому вони зберігають такі корисні функції (від VB) поза System.String в Framework, але все ще під Microsoft.VisualBasic. Той самий сценарій щодо фінансових функцій (наприклад Microsoft.VisualBasic.Financial.Pmt()).

public static string StrReverse(this string expression)
{
    if ((expression == null))
        return "";

    int srcIndex;

    var length = expression.Length;
    if (length == 0)
        return "";

    //CONSIDER: Get System.String to add a surrogate aware Reverse method

    //Detect if there are any graphemes that need special handling
    for (srcIndex = 0; srcIndex <= length - 1; srcIndex++)
    {
        var ch = expression[srcIndex];
        var uc = char.GetUnicodeCategory(ch);
        if (uc == UnicodeCategory.Surrogate || uc == UnicodeCategory.NonSpacingMark || uc == UnicodeCategory.SpacingCombiningMark || uc == UnicodeCategory.EnclosingMark)
        {
            //Need to use special handling
            return InternalStrReverse(expression, srcIndex, length);
        }
    }

    var chars = expression.ToCharArray();
    Array.Reverse(chars);
    return new string(chars);
}

///<remarks>This routine handles reversing Strings containing graphemes
/// GRAPHEME: a text element that is displayed as a single character</remarks>
private static string InternalStrReverse(string expression, int srcIndex, int length)
{
    //This code can only be hit one time
    var sb = new StringBuilder(length) { Length = length };

    var textEnum = StringInfo.GetTextElementEnumerator(expression, srcIndex);

    //Init enumerator position
    if (!textEnum.MoveNext())
    {
        return "";
    }

    var lastSrcIndex = 0;
    var destIndex = length - 1;

    //Copy up the first surrogate found
    while (lastSrcIndex < srcIndex)
    {
        sb[destIndex] = expression[lastSrcIndex];
        destIndex -= 1;
        lastSrcIndex += 1;
    }

    //Now iterate through the text elements and copy them to the reversed string
    var nextSrcIndex = textEnum.ElementIndex;

    while (destIndex >= 0)
    {
        srcIndex = nextSrcIndex;

        //Move to next element
        nextSrcIndex = (textEnum.MoveNext()) ? textEnum.ElementIndex : length;
        lastSrcIndex = nextSrcIndex - 1;

        while (lastSrcIndex >= srcIndex)
        {
            sb[destIndex] = expression[lastSrcIndex];
            destIndex -= 1;
            lastSrcIndex -= 1;
        }
    }

    return sb.ToString();
}

+1, приємне доповнення! Я щойно спробував це string s = "abo\u0327\u0307\u035d\U0001d166cd", який містить літеру, oа потім 3 комбінуючих діакритичні позначки в БМП та один комбінуючий знак (МУЗИЧНА СИМВОЛЬНА КОМБІНІКАЦІЯ) з астральної площини (не-BMP), і це зберігає їх недоторканими. Але метод повільний, якщо такі символи відображаються лише в кінці довгого рядка, оскільки він повинен двічі пройти весь масив.
Абель

3

Вибачте за публікацію в цій старій темі. Я практикую якийсь код для інтерв'ю.

Це те, що я придумав для C #. Моя перша версія до рефакторингу була жахливою.

static String Reverse2(string str)
{
    int strLen = str.Length, elem = strLen - 1;
    char[] charA = new char[strLen];

    for (int i = 0; i < strLen; i++)
    {
        charA[elem] = str[i];
        elem--;
    }

    return new String(charA);
}

На відміну від Array.Reverseметоду, наведеного нижче, він відображається швидше з 12 символами або менше у рядку. Після 13 символів Array.Reverseстарт стає швидшим, і в підсумку він досить сильно домінує на швидкості. Я просто хотів приблизно вказати, де швидкість починає змінюватися.

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

    Array.Reverse(charA);

    return new String(charA);
}

На 100 символів у рядку це швидше, ніж у моїй версії x 4. Однак, якби я знав, що рядки завжди будуть менше 13 символів, я б використав той, який я створив.

Тестування проводилось за допомогою Stopwatch5000000 ітерацій. Крім того, я не впевнений, чи мою версію обробляють сурогати чи комбіновані символьні ситуації з Unicodeкодуванням.


2

"Кращий шлях" залежить від того, що для вас важливіше у вашій ситуації, продуктивності, елегантності, ремонтопридатності тощо.

У будь-якому випадку, ось підхід із використанням Array.Reverse:

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

string reversed = new string(charArray);

2

Якщо вона коли-небудь з'явилася в інтерв'ю, і вам сказали, що ви не можете використовувати Array.Reverse, я думаю, це може бути одним з найшвидших. Він не створює нових рядків і ітераціює лише понад половину масиву (тобто ітерації O (n / 2))

    public static string ReverseString(string stringToReverse)
    {
        char[] charArray = stringToReverse.ToCharArray();
        int len = charArray.Length-1;
        int mid = len / 2;

        for (int i = 0; i < mid; i++)
        {
            char tmp = charArray[i];
            charArray[i] = charArray[len - i];
            charArray[len - i] = tmp;
        }
        return new string(charArray);
    }

2
Я досить впевнений, що виклик stringToReverse.ToCharArray () дасть час виконання O (N).
Марсель Вальдес Ороско

У нотації Big-O фактор, не залежний від xабо у вашому випадку, nне використовується. Ваш алгоритм має продуктивність f(x) = x + ½x + C, де C деяка константа. Оскільки Cі коефіцієнт не залежить від xвашого алгоритму O(x). Це не означає, що воно не буде швидшим для будь-якого введення довжини x, але його продуктивність лінійно залежить від вхідної довжини. Щоб відповісти на @MarcelValdezOrozco, так, це також O(n), хоча він копіює на 16-байтові шматки для покращення швидкості (не використовується пряма memcpyна загальну довжину).
Абель

2

Якщо у вас є рядок, що містить лише символи ASCII, ви можете використовувати цей метод.

    public static string ASCIIReverse(string s)
    {
        byte[] reversed = new byte[s.Length];

        int k = 0;
        for (int i = s.Length - 1; i >= 0; i--)
        {
            reversed[k++] = (byte)s[i];
        }

        return Encoding.ASCII.GetString(reversed);
    }

2

Перш за все, що ви повинні зрозуміти, це те, що str + = змінить розмір вашої струнної пам'яті, щоб звільнити місце для 1 додаткового знака. Це добре, але якщо у вас є, скажімо, книга з 1000 сторінками, яку ви хочете змінити, це займе дуже багато часу.

Рішення, яке можуть запропонувати деякі люди, - це використовувати StringBuilder. Що будівельник рядків робить, коли ви виконуєте + =, це те, що він виділяє набагато більші шматки пам’яті для утримання нового символу, щоб йому не потрібно було перерозподіляти кожен раз, коли ви додаєте символу.

Якщо ви дійсно хочете швидкого та мінімального рішення, я б запропонував наступне:

            char[] chars = new char[str.Length];
            for (int i = str.Length - 1, j = 0; i >= 0; --i, ++j)
            {
                chars[j] = str[i];
            }
            str = new String(chars);

У цьому рішенні є один початковий розподіл пам'яті при ініціалізації char [] та один розподіл, коли конструктор рядків будує рядок із масиву char.

У своїй системі я провів для вас тест, який обертає рядок у 2 750 000 символів. Ось результати для 10 страт:

StringBuilder: 190K - 200K кліщів

Char Array: 130K - 160K кліщів

Я також провів тест на нормальний String + =, але я відмовився від нього через 10 хвилин без вихідних даних.

Однак я також помітив, що для менших рядків StringBuilder швидше, тому вам доведеться приймати рішення про реалізацію на основі вхідних даних.

Ура


не працює для😀Les Misérables
Чарльз

@Charles Ага так, я думаю, є обмеження для встановлення статусу.
Reasurria

2
public static string reverse(string s) 
{
    string r = "";
    for (int i = s.Length; i > 0; i--) r += s[i - 1];
    return r;
}

1
public static string Reverse2(string x)
        {
            char[] charArray = new char[x.Length];
            int len = x.Length - 1;
            for (int i = 0; i <= len; i++)
                charArray[i] = x[len - i];
            return new string(charArray);
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.