LINQ独自オペレータ メモ
C#のIEnumerable
本記事では、あったら便利な独自のオペレータを定義してメモしたいと思います。
随時更新予定です。
ForEach
なんだかんだで、やっぱりあると便利です。
public static void ForEach<T>(this IEnumerable<T> source, Action<T> f) { foreach (T item in source) f(item); }
ForEach
引数にインデックスを渡すForEachです。
public static void ForEach<T>(this IEnumerable<T> source, Action<int, T> f) { int i = 0; foreach (T item in source) { f(i, item); i++; } }
ZipForEach
ZipとForEachを同時に行います。
public static void ZipForEach<T1, T2>(this IEnumerable<T1> source1, IEnumerable<T2> source2, Action<T1, T2> f) { var s1 = source1.GetEnumerator(); var s2 = source2.GetEnumerator(); while (s1.MoveNext() && s2.MoveNext()) f(s1.Current, s2.Current); }
ReplaceAt
あるインデックスの要素を置き換えます。
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, int index, T value) => source.Select((x, i) => i == index ? value : x);
UpdateAt
あるインデックスの要素を更新します。
public static IEnumerable<T> UpdateAt<T>(this IEnumerable<T> source, int index, Func<T, T> f) => source.Select((x, i) => i == index ? f(x) : x);
RemoveAt
あるインデックスの要素を削除します。
public static IEnumerable<T> RemoveAt<T>(this IEnumerable<T> source, int index) => source.Where((x, i) => i != index);
InsertAt
指定のインデックスに要素を追加します。
先頭は0、末尾はsource.Countを指定し、範囲外の場合はsourceそのままが出力されます。
public static IEnumerable<T> InsertAt<T>(this IEnumerable<T> source, int index, T value) { if (index == 0) yield return value; int i = 0; foreach (T item in source) { yield return item; i++; if (i == index) yield return value; } }
RemoveLast
最後の要素を削除します。(UpdateLastやInsertLastも同様に定義できます。)
public static IEnumerable<T> RemoveLast<T>(this IEnumerable<T> source) => source.RemoveAt(source.Count() - 1);
Scan
Aggregateの全過程を出力します。
public static IEnumerable<TAccumulate> Scan<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> func) { yield return seed; foreach (var item in source) { seed = func(seed, item); yield return seed; } }
Create
初期値と更新関数からIEnumerableを作成します。
public static IEnumerable<T> Create<T>(T seed, Func<T, T> func) { while (true) { yield return seed; seed = func(seed); } }
Flatten
木構造を配列に変換します。
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> seed, Func<T, IEnumerable<T>> func) { var queue = new Queue<T>(seed); while (queue.Any()) { T item = queue.Dequeue(); foreach (T subItem in func(item)) queue.Enqueue(subItem); yield return item; } }
Buffer
指定の要素数ごとにまとめたListに変換します。
public static IEnumerable<List<T>> Buffer<T>(this IEnumerable<T> source, int count) { var buffer = new List<T>(count); foreach (T item in source) { buffer.Add(item); if (buffer.Count == count) { yield return buffer; buffer = new List<T>(count); } } if (buffer.Any()) yield return buffer; }
Distinct(セレクタ関数で)
DistinctにはIEqualityComparerを取るものしか用意されていないので、セレクタ関数を取るように実装します。
class CompareSelector<T, TKey> : IEqualityComparer<T> { private Func<T, TKey> _selector; public CompareSelector(Func<T, TKey> selector) => _selector = selector; public bool Equals(T x, T y) => _selector(x).Equals(_selector(y)); public int GetHashCode(T obj) => _selector(obj).GetHashCode(); } public static IEnumerable<T> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) => source.Distinct(new CompareSelector<T, TKey>(selector));
Lines
stringを行ごとに分解
public static IEnumerable<string> Lines(this string source) { using (var reader = new StringReader(source)) { while (true) { var line = reader.ReadLine(); if (line == null) break; yield return line; } } }