スライシングでお世話になる範囲構文。
var span = new int[] { 1, 2, 3, 4, 5 }.AsSpan(); var a = span[..]; // 1, 2, 3, 4, 5 var b = span[3..]; // 4, 5 var c = span[..3]; // 1, 2, 3 var d = span[2..4]; // 3, 4 var e = span[^1..]; // 5 var f = span[..^1]; // 1, 2, 3, 4
その実体はRange
型(とそれが持つIndex
型)。
なので、引数にRange
を取るだけで範囲構文を受けることが可能。
処理でスライシングを使うなら、利用には引数で受けたRange
を使うだけ。
以下は指定された範囲の文字列を反転するメソッドの例。
public static string Reverse(this string str) => str.Reverse(Range.All); public static string Reverse(this string str, Range range) { return string.Create(str.Length, (range, str), static (buf, state) => { var (range, str) = state; str.AsSpan().CopyTo(buf); buf[range].Reverse(); }); }
以下のように範囲構文を受けることができる。
var str = "イーハトーヴォ"; Console.WriteLine(str.Reverse()); Console.WriteLine(str.Reverse(2..5)); Console.WriteLine(str.Reverse(..^1)); // ォヴートハーイ // イーートハヴォ // ヴートハーイォ
ところで、Range
にはGetOffsetAndLength(int)
という、コレクションの長さを利用してオフセット(開始インデックス)と長さを計算してくれるメソッドがある。
これを利用すると、スライシングを直接行わない範囲を表す処理に、Range
を簡単に導入できる。
以下のような累積和を表すクラスを考えてみる。
public class CumulativeSum<T> where T : struct, IAdditionOperators<T, T, T>, ISubtractionOperators<T, T, T> { private readonly T[] sums; public int Length => sums.Length - 1; public CumulativeSum(scoped ReadOnlySpan<T> span) { sums = new T[span.Length + 1]; sums[0] = default; for (int i = 0; i < span.Length; i++) { sums[i + 1] = sums[i] + span[i]; } } }
[left, right) 範囲をRange
で指定して区間和を取るインデクサは次のように書ける。
public T this[Range range] { get { var (offset, length) = range.GetOffsetAndLength(Length); return sums[offset + length] - sums[offset]; } }
GetOffsetAndLength(int)
のおかげでRange
からの変換を意識しなくて済む。
使用例。
参考:累積和を何も考えずに書けるようにする! - Qiita
var array = new int[] { 2, 5, -4, 10, 3 }; var sum = new CumulativeSum<int>(array); int N = 5; int K = 3; int result = int.MinValue; for (int i = 0; i < N - K; i++) { var current = sum[i..(i + K)]; if (result < current) result = current; } Console.WriteLine(result); // 11
ちなみに、Range
はreadonly struct
なので引数においては値の参照渡し(in
引数)が浮かぶが、サイズがint
ふたつぶんということで値渡しでよさそう。
参考:参照渡し - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
なお、Enumerable.Take(Range)
では値渡しだった。