LINQ独自オペレータ メモ

C#のIEnumerableには、SelectやAggregateなど様々なオペレータが用意されています。

本記事では、あったら便利な独自のオペレータを定義してメモしたいと思います。

随時更新予定です。

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