てくメモ

trivial な notes

【C#】文字列を逆順にする

文字列を逆順にする(「あいう」→「ういあ」みたいな意味)ときにLINQを使うというネタがある。

// string text;
public string ReverseByLINQ() => new(text.Reverse().ToArray());

ネタというのは、LINQは分かりやすくて書きやすいけれどパフォーマンスと天秤、みたいな感じで出る話題的な意味。
(※ と思ったけれど記事化にあたりググったところ、普通に単一の手段として紹介しているサイトが複数あった。別にそういう捉え方はされていなかったかもしれない)


文字列の取り扱いの注意点も盛り込める。

public string ReverseByStringConcat() => text.Reverse().Aggregate("", (str, c) => str + c);

こういうことをすると毎回文字列が new されちゃうよね、というネタ。


ネタ以外では?

文字列はStringBuilderが王道という雰囲気。
Reverse()もやめて、愚直にループ。

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


連続したメモリへの操作ということでSpanも。

public string ReverseBySpanReverse()
{
    var span = text.AsSpan();
    Span<char> buf = stackalloc char[span.Length];
    span.CopyTo(buf);
    buf.Reverse();
    return buf.ToString();
}

ループしてもよいけれど、SpanにはReverse()がある。これは内部で、先頭と末尾から参照を交換していくunsafeな処理をしている。


というわけでBenchmarkDotNet。

string text = "あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。";
                 Method |         Mean |       Error |     StdDev |   Gen0 | Allocated |
----------------------- |-------------:|------------:|-----------:|-------:|----------:|
          ReverseByLINQ |   704.153 ns |  98.8347 ns |  5.4175 ns | 0.2728 |     856 B |
  ReverseByStringConcat | 2,786.224 ns | 315.3830 ns | 17.2872 ns | 2.6054 |    8176 B |
 ReverseByStringBuilder |   175.187 ns |  20.1415 ns |  1.1040 ns | 0.1147 |     360 B |
   ReverseBySpanReverse |    48.673 ns |   7.7572 ns |  0.4252 ns | 0.0485 |     152 B |


やはりアロケーションや、ライブラリが内部でやっていることの関係でSpanが強かった。


ところで、Reverse()は副作用のあるメソッドなので、ReadOnlySpan<char>には生えていない。
これが許されたら、不変として扱われるstringが書き換わってしまう。マネージドで安全なC#では許されようはずがない。


―― まぁちょっとくらいなら……

public string ReverseDestructive()
{
    var readOnlySpan = text.AsSpan();
    var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(readOnlySpan), readOnlySpan.Length);
    span.Reverse();
    return text; // 書き換わっている
}
                 Method |         Mean |      Error |    StdDev |   Gen0 | Allocated |
----------------------- |-------------:|-----------:|----------:|-------:|----------:|
     ReverseDestructive |     9.093 ns |   0.7682 ns |  0.0421 ns |      - |         - |


実用性はないので、文字列を逆順にするネタとして。