Bir dizeyi ters çevirmenin en iyi yolu


440

Sadece C # 2.0 (yani LINQ kullanılabilir değil) bir dize ters işlevi yazmak zorunda kaldı ve bu ile geldi:

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

Şahsen işlev hakkında deli değilim ve bunu yapmanın daha iyi bir yolu olduğuna ikna oldum. Var mı?


51
Doğru uluslararası destek istiyorsanız şaşırtıcı derecede zor. Örnek: Hırvatça / Sırpça iki karakterli lj, nj vb. Harflere sahiptir. "Ljudi" nin tam tersi "idulj", "idujl" DEĞİLDİR. Arapça, Tayca vb.
Söz

Bir temp dizisi başlatma ve sonuçları saklamak yerine bir dize concat için yavaş olup olmadığını ve nihayet bir dize dönüştürmek merak ediyorum?
Muffin Adam


5
Bu soru, "en iyi" ile ne demek istediğinizi tanımlayarak geliştirilebilir. En hızlı? En okunabilir? Çeşitli uç durumlarda en güvenilir olan (boş denetimler, birden çok dil vb.)? En çok C # ve .NET sürümleri arasında bakımı yapılabilir mi?
hypehuman

Yanıtlar:


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

16
sambo99: Unicode'dan bahsetmek gerekmez: C # 'daki karakterlerin bayt değil, unicode karakterleri vardır. Xor daha hızlı olabilir, ancak daha az okunabilir olmanın yanı sıra, Array.Reverse () 'in dahili olarak kullandığı şey de olabilir.
Nick Johnson

27
@Arachnid: Aslında, C # 'daki karakter UTF-16 kod birimleri; tamamlayıcı bir karakteri temsil etmek için iki tane gerekir. Bkz. Jaggersoft.com/csharp_standard/9.4.1.htm .
Bradley Grainger

4
Evet sambo99 Sanırım haklısınız, ancak UTF-32'yi kullanmak oldukça nadir bir durum. Ve XOR sadece çok küçük bir değer aralığı için daha hızlıdır, doğru cevap sanırım farklı uzunluklar için farklı yöntemler uygulamak olacaktır. Ama bu açık ve özlü ki bence bir fayda.
PeteT

21
Unicode kontrol karakterleri bu yöntemi latin olmayan karakter kümeleri için işe yaramaz hale getirir. Çorap kuklası kullanarak Jon Skeet açıklamasına bakın: codeblog.jonskeet.uk/2009/11/02/… (1/4 yol aşağı) veya video: vimeo.com/7516539
Callum Rogers

20
Umarım suretlerle veya karakterleri birleştirmezsiniz.
dalle

183

İşte düzgün dize tersine bir çözüm "Les Mise\u0301rables"olarak "selbare\u0301siM seL". Bu , kod birimlerine ( vb.) Veya hatta kod noktalarına (vekil çiftler için özel bir özenle geri döndürme) dayalı çoğu uygulamanın sonucu olarak olduğu gibi (aksanın konumunu not edin) selbarésiM seLdeğil gibi olmalıdır .selbaŕesiM seLArray.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);
    }
}

(Ve canlı koşu örneği burada: https://ideone.com/DqAeMJ )

Görünüşe göre , her zamandan beri orada olan grafik küme yinelemesi için .NET API'yi kullanıyor , ancak görünümden biraz "gizli" görünüyor.


10
Çok az doğru cevap +1 Bir ve çok IMO başkalarının daha daha zarif ve gelecekteki geçirmez,
sehe

Ancak bu, yerel ayarlara bağlı bazı şeyler için başarısız olur.
R. Martinho Fernandes

7
Diğer cevaplayıcıların çoğunun ms'yi yanlış yaklaşımların ne olduğu konusunda tıraş etmeye çalıştığı komik. Ne kadar temsilci.
G. Stoynev

2
StringInfo (lar) ı örneklemek, sonra SubstringByTextElements (x, 1) üzerinden yineleme yapmak ve StringBuilder ile yeni bir dize oluşturmak aslında çok daha hızlıdır.

2
Jon Skeet'in yıllar önce codeblog.jonskeet.uk/2009/11/02/… Les Misérables'ı verdiği örneğini kullanmanız biraz garip (Jon bir çözümden bahsetmemiş olsa da sorunları listeledi). Bir çözüm bulman iyi oldu. Belki Jon skeet bir zaman makinesi icat etti, 2009'a geri döndü ve çözümünüzde kullandığınız sorun örneğini gönderdi.
barlop

126

Bu şaşırtıcı derecede zor bir soru olarak ortaya çıkıyor.

Array.Riveerse'ı yerel olarak kodlandığı ve bakımı ve anlaşılması çok basit olduğu için tersine çevirmenizi öneririm.

Test ettiğim tüm durumlarda StringBuilder'den daha iyi performans gösteriyor gibi görünüyor.

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 kullanan belirli dize uzunlukları için daha hızlı olabilen ikinci bir yaklaşım vardır .

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

Not Tam Unicode UTF16 karakter setini desteklemek istiyorsanız bunu okuyun . Ve bunun yerine uygulamayı kullanın. Yukarıdaki algoritmalardan birini kullanarak ve karakterleri tersine çevirdikten sonra temizlemek için dizeyi kullanarak daha da optimize edilebilir.

İşte StringBuilder, Array.Reverse ve Xor yöntemi arasındaki performans karşılaştırması.

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

Sonuçlar burada:

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.

Görünüşe göre Xor kısa teller için daha hızlı olabilir.


2
Bu bir dize döndürmez - bunu "yeni Dize (...)" çağrısında sarmanız gerekir
Greg Beech

BTW .. Ben sadece Array.Reverse uygulanmasına bir göz vardı ve onun chars için naitively yapılır ... Bu StringBuilder seçeneğinden çok daha hızlı olmalıdır.
Sam Saffron

Sambo'yu durdurmak için ne kadar hoş geldiniz Greg, onu oy kullanmak yerine daha iyi bir çözüme ulaşır.
DOK

@ dok1 - bahsetme :) @ sambo99 - şimdi merak ediyorum, yarın bir kod profiler kırbaçlamak ve bir göz atmak zorunda kalacak!
Greg Beech

9
Bu yöntemler, Temel Çok Dilli Düzlem'in dışındaki karakterleri içeren dizeleri işlemez; diğer bir deyişle, iki C # karakteriyle temsil edilen Unicode karakterler> = U + 10000. Böyle dizeleri doğru işleyen bir cevap gönderdim.
Bradley Grainger

52

LINQ (.NET Framework 3.5+) kullanabiliyorsanız, bir astar size kısa kod verecektir. Aşağıdakilere using System.Linq;erişebilmek için eklemeyi unutmayın Enumerable.Reverse:

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

Notlar:

  • en hızlı sürüm değil - Martin Niederl'e göre burada en hızlı seçimden 5.7 kat daha yavaş.
  • Birçok diğer seçenekler olarak bu kod tamamen, çok karakterli kombinasyonlarının her türlü ihmal ev ödevlerini ve dizeleri sınırı kullanımı bu yüzden yok böyle karakterler içeriyor. Bu tür kombinasyonları doğru şekilde işleyen uygulama için bu sorudaki başka bir cevaba bakınız .

Bu en çok upvoted sürümünden yaklaşık 5.7 kat daha yavaş bu yüzden bunu kullanmanızı tavsiye etmem!
Martin Niederl

2
En hızlı çözüm değil, tek astar olarak kullanışlıdır.
adrianmp

49

Dize Unicode verileri içeriyorsa (kesin olarak konuşursak, BMP olmayan karakterler), dizeyi tersine çevirirken yüksek ve düşük vekil kod birimlerinin sırasını değiştiremeyeceğiniz için, gönderilen diğer yöntemler verileri bozacaktır. (Bununla ilgili daha fazla bilgiyi blogumda bulabilirsiniz .)

Aşağıdaki kod örneği, BMP olmayan karakterler içeren bir dizeyi doğru şekilde ters çevirecektir, örneğin, "\ 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
Aslında, C # 'da karakter 16-bit UTF-16 kod birimleri; tamamlayıcı bir karakter ikisi kullanılarak kodlanır, bu yüzden bu gereklidir,
Bradley Grainger

14
Görünüşe göre System.String, Unicode ek karakterleri içeren dizeler için bir HereBeDragons özelliğini göstermelidir.
Robert Rossney

4
@SebastianNegraszus: Bu doğru: bu yöntem dizedeki kod noktalarını tersine çevirir. Geri vites sesletim kümeleri muhtemelen daha genel "yararlı" (ama ne ilk etapta keyfi bir dize ters "kullanımı" mı?) Olabilir, ancak birlikte uygulanması kolay değildir tıpkı yerleşik .NET Framework yöntemlerle.
Bradley Grainger

2
@Richard: Grafik kümelerini kırmaya yönelik kurallar, yalnızca kod noktalarını birleştirmeyi tespit etmekten biraz daha karmaşıktır; daha fazla bilgi için UAX # 29'daki Grafik Kümesi Sınırları hakkındaki belgelere bakın .
Bradley Grainger

1
Çok iyi bilgi! Does HERKES Array.Reverse testi için başarısız test var? Ve testle demek istediğim, bütün bir birim testi değil, örnek bir dizi ... Bana (ve diğerlerine) bu konuyla ilgili farklı insanları ikna etmeme gerçekten yardımcı olurdu ..
Andrei Rînea

25

Tamam, "kendini tekrar etme" yararına, aşağıdaki çözümü öneriyorum:

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

Anladığım kadarıyla, VB.NET'te varsayılan olarak bulunan bu uygulama, Unicode karakterlerini düzgün bir şekilde işliyor.


11
Bu sadece taşıyıcı vekilleri düzgün şekilde idare eder. İşaretleri birleştirerek karıştırır: ideone.com/yikdqX .
R. Martinho Fernandes

17

Greg Beech unsafegerçekten olabildiğince hızlı bir seçenek yayınladı (bu bir ters dönüş); ama cevabında belirttiği gibi, bu tamamen felaket bir fikir .

Bununla birlikte Array.Reverse, en hızlı yöntem olan bir fikir birliği olduğu için şaşırdım . Hala bir unsafedizenin ters kopyasını (yerinde ters çevirme parlaklığı yok) küçük dizeler yönteminden önemli ölçüde daha hızlıArray.Reverse döndüren bir yaklaşım var :

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

İşte bazı kıyaslama sonuçları .

Performans kazancının küçüldüğünü ve sonra Array.Reversedizeler büyüdükçe yönteme karşı kaybolduğunu görebilirsiniz . Bununla birlikte, küçük ve orta ölçekli dizeler için bu yöntemi yenmek zordur.


2
Büyük dizelerde StackOverflow.
Raz Megrelidze

@rezomegreldize: Evet, bu olacak;)
Dan Tao

15

Kolay ve güzel cevap Uzantı Yöntemi'ni kullanmaktır:

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

ve işte çıktı:

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

Reverse()ve ToArray()kod örneğinizde yanlış sırada.
Chris Walsh

@ Ne işe yarar?
user5389726598465

2
@ user5389726598465 Bu bağlantıya bakın: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 'base', C # 'da bir anahtar kelime olduğundan, C # derleyicisinin bir # tanıtıcı.
Dyndrilliac

14

Gerçekten tehlikeli bir oyun oynamak istiyorsanız, o zaman bu en hızlı yoldur ( Array.Reverseyöntemden yaklaşık dört kat daha hızlı ). Bu işaretçiler kullanarak yerinde tersidir.

Bunu herhangi bir kullanım için gerçekten önermediğimi unutmayın ( bu yöntemi kullanmamanız için bazı nedenlerden dolayı buraya bakın ), ancak bunun yapılabileceğini ve dizelerin gerçekten değişmez olmadığını görmek ilginç güvensiz kodu açtığınızda.

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

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

        return text;
    }
}

Im emin bu utf16 dizeleri için yanlış sonuçlar döndürecek, bu gerçekten sorun soruyor :)
Sam Saffron

Merhaba, bu gerçekten sorun için soruyor önce söylediğim gibi , bu stackoverflow.com/questions/229346/… bu yazı link gerekir ...
Sam Saffron

Bu tamamen kötü ve tavsiye edilmemiş olabilir (siz kendiniz kabul ettiğiniz gibi), ancak kötü olmayan ve çoğu durumda yine de atılan unsafekodu kullanarak bir dizgeyi tersine çevirmenin yüksek performanslı bir yolu vardır . Cevabıma bir göz atın. Array.Reverse
Dan Tao

13

Buradaki wikipedia girişine bir göz atın . String.Reverse uzantı yöntemini uygularlar. Bu, şöyle bir kod yazmanıza olanak tanır:

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

Ayrıca, bu soruya verilen diğer yanıtların önerdiği ToCharArray / Reverse kombinasyonunu kullanırlar. Kaynak kodu şuna benzer:

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

Bu harika, ancak uzantı yöntemleri c # 2.0'da tanıtılmadı.
Kobi

11

Öncelikle ToCharArraybir karakter dizisi olarak zaten dizine eklenebileceğinden bir dize çağırmanıza gerek yoktur , bu nedenle bu size bir ayırma tasarrufu sağlayacaktır.

Bir sonraki optimizasyon, StringBuildergereksiz ayırmaları önlemek için a kullanmaktır (dizeler değişmez olduğundan, bunları birleştirmek her seferinde dizenin bir kopyasını oluşturur). Bunu daha da optimize etmek için, uzunluğunu önceden ayarladık, StringBuilderböylece tamponunu genişletmeye gerek kalmayacak.

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

Düzenleme: Performans Verileri

Bu işlevi ve işlevi Array.Reverseaşağıdaki basit programla kullanarak test ettim , burada Reverse1bir işlev ve Reverse2diğeri:

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

Kısa dizeler için Array.Reverseyöntemin yukarıdaki yöntemden iki kat daha hızlı olduğu ve daha uzun dizeler için farkın daha da belirgin olduğu ortaya çıktı. Bu yüzden Array.Reverseyöntemin hem daha basit hem de daha hızlı olduğu göz önüne alındığında, bunu kullanmak yerine bunu kullanmanızı tavsiye ederim. Bunu yapman gereken yolun bu olmadığını göstermek için burada bırakıyorum (benim için sürpriz!)


Bir değişkendeki uzunluk, bir nesne üzerinden referansta bulunduğunuzda biraz daha fazla hız verir mi?
David Robbins

10

Array'ı kullanmayı deneyin.


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

Bu inanılmaz derecede hızlı.
Michael Stum

Neden aşağı oy? Tartışmak değil, ama hatalarımdan ders almayı tercih ederim.
Mike Two

Diğer birçok şey arasında kod noktalarını birleştirmeyi başaramaz.
Mooing Duck

@MooingDuck - açıkladığınız için teşekkürler, ancak kod noktalarıyla ne demek istediğinizi bilmiyorum. Ayrıca "başka birçok şey" üzerinde de durulabilirdiniz.
Mike Two

@MooingDuck Kod noktalarını araştırdım. Evet. Haklısın. Kod noktalarını işlemez. Bu kadar basit görünen bir soru için tüm gereklilikleri belirlemek zordur. Geri bildiriminiz için teşekkürler
Mike Two

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

Elbette dize sınıfını Tersine yöntemiyle genişletebilirsiniz

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

Enumerable.Reverse(input)eşittirinput.Reverse()
fubo

8

"En iyi" birçok şeye bağlı olabilir, ancak hızlıdan yavaşa doğru sıralanan birkaç kısa alternatif daha:

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 ile başlayarak string.Createyöntemi kullanarak bir dizeyi tersine çevirmenin yeni bir yolu vardır .

"Les Mise \ u0301rables", "selbarésiM seL" biçimine dönüştürüleceğinden, bu çözümün Unicode birleştirme karakterlerini vb. Doğru şekilde işlemediğini unutmayın. Diğer cevaplar , daha iyi bir çözüm.

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

Bu aslında karakterlerini inputyeni bir dizeye kopyalar ve yeni dizeyi yerinde tersine çevirir.

Neden string.Createfaydalı?

Mevcut bir diziden dize oluşturduğumuzda, yeni bir dahili dizi ayrılır ve değerler kopyalanır. Aksi takdirde, bir dizeyi oluşturulduktan sonra (güvenli bir ortamda) değiştirmek mümkün olabilir. Yani, aşağıdaki kod parçasında, biri arabellek, diğeri de dizenin iç dizisi olmak üzere iki kez 10 uzunluğunda bir dizi ayırmamız gerekir.

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

string.Createtemelde dize oluşturma süresi boyunca dahili diziyi değiştirmemize izin verir. Bu, artık bir tampona ihtiyacımız yok ve bu nedenle bu char dizisini ayırmaktan kaçınabilir.

Steve Gordon burada daha ayrıntılı olarak yazmıştır . MSDN'de de bir makale var .

Nasıl kullanılır string.Create?

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

Yöntem üç parametre alır:

  1. Oluşturulacak dizenin uzunluğu,
  2. yeni dizeyi dinamik olarak oluşturmak için kullanmak istediğiniz veriler,
  3. ve verilerden son dizeyi oluşturan bir temsilci; burada birinci parametre charyeni dizenin iç dizisini gösterir ve ikincisi de geçtiğiniz veri (durum) 'dur string.Create.

Temsilcinin içinde, yeni dizenin verilerden nasıl oluşturulacağını belirtebiliriz. Bizim durumumuzda, sadece giriş dizesinin karakterlerini Spanyeni dizenin kullandığı yere kopyalarız . Sonra geriSpan ve böylece tüm dize tersine çevrilir.

Deneyler

Bir dizeyi tersine çevirmek için önerilen yolumu kabul edilen cevapla karşılaştırmak için BenchmarkDotNet'i kullanarak iki kriter yazdım.

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

İşte makinemdeki sonuçlar:

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

Gördüğünüz gibi ReverseWithStringCreate, ReverseWithArrayyöntem tarafından kullanılan belleğin sadece yarısını tahsis ediyoruz .


Linq tersine göre çok daha hızlı
code4j

7

Bir işlevle uğraşmayın, sadece yerinde yapın. Not: İkinci satır, bazı VS sürümlerinin Hemen penceresinde bir argüman istisnası atar.

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

1
Bazı adamlar, her cevabı (benimki dahil) nedenini açıklamadan küçümsemek için zaman aldı.
Marcel Valdez Orozco

Eğer bir oluştururken, bu durum yerinde gerçekten değilnew string
mbadawi23

5

Uzun yazı için özür dilerim, ama bu ilginç olabilir

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

Sonuçlar:

  • ReverseUsingCharacterBuffer: 346ms
  • ReverseUsingArrayClass: 87 ms
  • Ters KullanımStringBuilder: 824ms
  • ReverseUsingStack: 2086 ms
  • Ters KullanımXOR: 319ms

Yazıma benzer bir karşılaştırma ekledim, bu bir topluluk wiki'si, böylece düzenleyebilmelisiniz. Performans gerçekten dizenin uzunluğuna ve algoritmaya bağlıdır, onu grafiklemek ilginç olacaktır. Ben hala Array.Reverse tüm durumlarda en hızlı olacağını düşünüyorum ...
Sam Saffron

Büyülü TrySZReverse işlevi (Ters uygulamada kullanılır) başarısız olduğunda, "her durumda en hızlı olacak" Array.Kutu içeren basit uygulamaya geri dönüşler, böylece benim yöntem kazanacak. Ancak TrySZReverse başarısız yapmak için bir koşul nedir bilmiyorum.
aku

Her durumda onun en hızlı değil olduğu ortaya çıkıyor :), yazımı güncelledim. Bunun hala hem doğruluk hem de hız açısından unicode ile test edilmesi gerekiyor.
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();
    }
}

Çıktı

Boyut için: 10

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

Boyut için: 100

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

Boyut için: 1000

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

Boyut için: 10000

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

1
Boş dizeyi kontrol etmeniz gerekiyor Reverse(...). Aksi takdirde, iyi iş.
Lara


4

Yığın tabanlı çözüm.

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

Veya

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

4

Özyinelemeli bir örnek sunmak zorunda kaldım:

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 uzunluğu dize ele
alınmaz

Bu yararlı değil.
user3613932

3

Nasıl olur:

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

Yukarıdaki diğer yöntemlerle aynı kod noktası sorunlarına sahiptir ve bir ToCharArrayilk yapmaktan çok daha yavaş çalışacaktır . LINQ numaralandırıcıdan çok daha yavaştır Array.Reverse().
Abel

3

Microsoft.VisualBasic.Strings bir C # bağlantı noktası yaptım . Neden bu tür yararlı işlevleri (VB) System.String Framework dışında, ama yine de Microsoft.VisualBasic altında tutmak emin değilim. Finansal fonksiyonlar için aynı senaryo (örn. 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, güzel bir ek! Ben sadece denedim string s = "abo\u0327\u0307\u035d\U0001d166cd", oardından BMP'de aksan işaretleri birleştiren 3 harfi ve astral düzlemden (BMP olmayan) bir birleştirici işareti (MÜZİK SEMBOL KOMBİNASYON KÖKÜ) içerir ve onları sağlam tutar. Ancak, bu karakterler sadece uzun bir dizenin sonunda görünürse, yöntem yavaştır, çünkü tüm dizinin iki katına gitmelidir.
Abel

3

Bu eski konuya gönderdiğim için üzgünüm. Röportaj için bazı kodlar uyguluyorum.

C # için bulduğum şey buydu. Yeniden düzenleme öncesi ilk versiyonum korkunçtu.

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.ReverseAşağıdaki yöntemin aksine , dizede 12 veya daha az karakterle daha hızlı görünür. 13 karakterden sonra, Array.Reversedaha hızlı olmaya başlar ve sonunda hızda oldukça ağırdır. Sadece hızın değişmeye başladığı yeri belirtmek istedim.

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

    Array.Reverse(charA);

    return new String(charA);
}

Dize 100 karakter, benim sürüm x 4 daha hızlı. Ancak, dizeleri her zaman 13 karakterden daha az olacağını biliyor olsaydım, ben yaptığımı kullanmak istiyorum.

Test Stopwatchve 5000000 iterasyon ile yapıldı. Ayrıca, sürümümün Taşıyıcıları mı yoksa Unicodekodlama ile birleştirilmiş karakter durumlarını mı işlediğinden emin değilim .


2

"Daha iyi yol" sizin durumunuz, performansınız, zarafetiniz, sürdürülebilirliğiniz vb. İçin sizin için neyin daha önemli olduğuna bağlıdır.

Her neyse, işte Array kullanarak bir yaklaşım.

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

string reversed = new string(charArray);

2

Eğer bir röportajda ortaya çıktıysa ve Array'ı kullanamayacağınız söylendi. Yeni dizeler oluşturmaz ve dizinin yalnızca yarısından fazlasını yineler (yani O (n / 2) yinelemeleri)

    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 () çağrısı bir O (N) yürütme süresi üretecek oldukça eminim.
Marcel Valdez Orozco

In The Big-O notasyonu , değil bağımlı faktör xya sizin durumda, n, kullanılmaz. Algoritmanız performansa sahiptir f(x) = x + ½x + C, burada C sabittir. Her ikisi de Cve faktör bağımlı olmadığından xalgoritmanız değişir O(x). Bu, herhangi bir uzunluk girişi için daha hızlı olmayacağı anlamına gelmez x, ancak performansı doğrusal olarak giriş uzunluğuna bağlıdır. @MarcelValdezOrozco'e cevap vermek için evet, aynı zamanda O(n), hızı artırmak için 16 baytlık parçalar başına kopyalar ( memcpytoplam uzunlukta düz kullanmaz ).
Abel

2

Yalnızca ASCII karakterleri içeren bir dizeniz varsa, bu yöntemi kullanabilirsiniz.

    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

Öncelikle anlamanız gereken şey str + = dizesinin 1 ekstra karakter için yer açmak için dize belleğinizi yeniden boyutlandırmasıdır. Bu iyi, ancak diyelim ki 1000 sayfalık geri çevirmek istediğiniz bir kitabınız varsa, bunun yürütülmesi çok uzun sürecektir.

Bazı kişilerin önerebileceği çözüm StringBuilder kullanmaktır. + = Gerçekleştirdiğinizde dize oluşturucunun yaptığı, yeni karakteri tutmak için çok daha büyük bellek parçaları ayırmasıdır, böylece her karakter eklediğinizde yeniden tahsis edilmesine gerek kalmaz.

Gerçekten hızlı ve minimal bir çözüm istiyorsanız aşağıdakileri öneririm:

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

Bu çözümde, char [] başlatıldığında bir ilk bellek ayırma ve dize yapıcısı char dizesinden char oluşturduğunda bir ayırma vardır.

Sistemimde senin için 2 750 000 karakterlik bir dizeyi tersine çeviren bir test yaptım. İşte 10 yürütmenin sonuçları:

StringBuilder: 190K - 200K keneler

Karakter Dizisi: 130K - 160K keneler

Ayrıca normal String + = için bir test çalıştırdım ama çıkış olmadan 10 dakika sonra terk ettim.

Ancak, daha küçük dizeler için StringBuilder daha hızlı olduğunu fark ettim, bu yüzden girdiye dayalı uygulamaya karar vermeniz gerekecek.

Şerefe


için çalışmıyor😀Les Misérables
Charles

@Charles Ah evet sanırım karakter seti sınırlaması var.
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);
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.