Какой ваш любимый оператор LINQ to Objects, который не является встроенным? - PullRequest
73 голосов
/ 05 сентября 2010

С помощью методов расширения мы можем написать удобные операторы LINQ, которые решают общие проблемы.

Я хочу услышать, какие методы или перегрузки отсутствуют в пространстве имен System.Linq и как вы их реализовали.

Предпочтительны чистые и элегантные реализации, возможно, с использованием существующих методов.

Ответы [ 43 ]

7 голосов
/ 05 сентября 2010

MinElement

Min возвращает только минимальное значение, возвращаемое указанным выражением, но не исходный элемент, который дал этот минимальный элемент.

7 голосов
/ 06 сентября 2010

Петля

Вот такой крутой, о котором я только что подумал.(Если бы я просто подумал об этом, может быть, это не так полезно? Но я подумал об этом, потому что у меня есть для этого применение.) Несколько раз просматривайте последовательность, чтобы сгенерировать бесконечную последовательность.Это выполняет нечто вроде того, что дают Enumerable.Range и Enumerable.Repeat, за исключением того, что оно может использоваться для произвольного (в отличие от Range) последовательности (в отличие от Repeat):

public static IEnumerable<T> Loop<T>(this IEnumerable<T> source)
{
    while (true)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

Использование:

var numbers = new[] { 1, 2, 3 };
var looped = numbers.Loop();

foreach (int x in looped.Take(10))
{
    Console.WriteLine(x);
}

Вывод:

1
2
3
1
2
3
1
2
3
1

Примечание: Я полагаю, вы также можете сделать это с помощью чего-то вроде:

var looped = Enumerable.Repeat(numbers, int.MaxValue).SelectMany(seq => seq);

... но я думаю Loop яснее.

6 голосов
/ 05 сентября 2010

Chunks

Возвращает куски определенного размера.x.Chunks(2) из 1,2,3,4,5 вернет два массива с 1,2 и 3,4.x.Chunks(2,true) вернет 1,2, 3,4 и 5.

public static IEnumerable<T[]> Chunks<T>(this IEnumerable<T> xs, int size, bool returnRest = false)
{
    var curr = new T[size];

    int i = 0;

    foreach (var x in xs)
    {
        if (i == size)
        {
            yield return curr;
            i = 0;
            curr = new T[size];
        }

        curr[i++] = x;
    }

    if (returnRest)
        yield return curr.Take(i).ToArray();
}
6 голосов
/ 05 сентября 2010

IndexOf

/// <summary>
/// Returns the index of the first element in this <paramref name="source"/>
/// satisfying the specified <paramref name="condition"/>. If no such elements
/// are found, returns -1.
/// </summary>
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> condition)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (condition == null)
        throw new ArgumentNullException("condition");
    int index = 0;
    foreach (var v in source)
    {
        if (condition(v))
            return index;
        index++;
    }
    return -1;
}
6 голосов
/ 05 сентября 2010

ToHashSet

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}
5 голосов
/ 05 сентября 2010

FirstOrDefault со значением по умолчанию

/// <summary>
/// Returns the first element of a sequence, or a default value if the
/// sequence contains no elements.
/// </summary>
/// <typeparam name="T">The type of the elements of
/// <paramref name="source"/>.</typeparam>
/// <param name="source">The <see cref="IEnumerable&lt;T&gt;"/> to return
/// the first element of.</param>
/// <param name="default">The default value to return if the sequence contains
/// no elements.</param>
/// <returns><paramref name="default"/> if <paramref name="source"/> is empty;
/// otherwise, the first element in <paramref name="source"/>.</returns>
public static T FirstOrDefault<T>(this IEnumerable<T> source, T @default)
{
    if (source == null)
        throw new ArgumentNullException("source");
    using (var e = source.GetEnumerator())
    {
        if (!e.MoveNext())
            return @default;
        return e.Current;
    }
}

/// <summary>
/// Returns the first element of a sequence, or a default value if the sequence
/// contains no elements.
/// </summary>
/// <typeparam name="T">The type of the elements of
/// <paramref name="source"/>.</typeparam>
/// <param name="source">The <see cref="IEnumerable&lt;T&gt;"/> to return
/// the first element of.</param>
/// <param name="predicate">A function to test each element for a
/// condition.</param>
/// <param name="default">The default value to return if the sequence contains
/// no elements.</param>
/// <returns><paramref name="default"/> if <paramref name="source"/> is empty
/// or if no element passes the test specified by <paramref name="predicate"/>;
/// otherwise, the first element in <paramref name="source"/> that passes
/// the test specified by <paramref name="predicate"/>.</returns>
public static T FirstOrDefault<T>(this IEnumerable<T> source,
    Func<T, bool> predicate, T @default)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (predicate == null)
        throw new ArgumentNullException("predicate");
    using (var e = source.GetEnumerator())
    {
        while (true)
        {
            if (!e.MoveNext())
                return @default;
            if (predicate(e.Current))
                return e.Current;
        }
    }
}
5 голосов
/ 05 сентября 2010

EmptyIfNull

Это спорный вопрос; Я уверен, что многие пуристы будут возражать против "метода экземпляра" на null успешно.

/// <summary>
/// Returns an IEnumerable<T> as is, or an empty IEnumerable<T> if it is null
/// </summary>
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}    

Использование:

foreach(var item in myEnumerable.EmptyIfNull())
{
  Console.WriteLine(item);   
}
5 голосов
/ 05 сентября 2010

InsertBetween

Вставляет элемент между каждой парой последовательных элементов.

/// <summary>Inserts the specified item in between each element in the input
/// collection.</summary>
/// <param name="source">The input collection.</param>
/// <param name="extraElement">The element to insert between each consecutive
/// pair of elements in the input collection.</param>
/// <returns>A collection containing the original collection with the extra
/// element inserted. For example, new[] { 1, 2, 3 }.InsertBetween(0) returns
/// { 1, 0, 2, 0, 3 }.</returns>
public static IEnumerable<T> InsertBetween<T>(
    this IEnumerable<T> source, T extraElement)
{
    return source.SelectMany(val => new[] { extraElement, val }).Skip(1);
}
5 голосов
/ 06 сентября 2010

Анализировать

В этом случае используется пользовательский делегат (мог бы использовать вместо него интерфейс IParser<T>, но я использовал делегат, поскольку он был проще), который используется для анализа последовательности строк в последовательности значений, пропуская элементы, где синтаксический анализ не удается.

public delegate bool TryParser<T>(string text, out T value);

public static IEnumerable<T> Parse<T>(this IEnumerable<string> source,
                                      TryParser<T> parser)
{
    source.ThrowIfNull("source");
    parser.ThrowIfNull("parser");

    foreach (string str in source)
    {
        T value;
        if (parser(str, out value))
        {
            yield return value;
        }
    }
}

Использование:

var strings = new[] { "1", "2", "H3llo", "4", "five", "6", "se7en" };
var numbers = strings.Parse<int>(int.TryParse);

foreach (int x in numbers)
{
    Console.WriteLine(x);
}

Выход:

1
2
4
6

Называть сложно для этого. Я не уверен, является ли Parse лучшим вариантом (по крайней мере, просто ) или что-то вроде ParseWhereValid будет лучше.

4 голосов
/ 05 сентября 2010

ZipMerge

Это моя версия Zip, которая работает как настоящая молния. Он не проецирует два значения в одно, а возвращает комбинированный IEnumerable. Возможны перегрузки, пропуская правый и / или левый хвост.

public static IEnumerable<TSource> ZipMerge<TSource>(
        this IEnumerable<TSource> first,
        IEnumerable<TSource> second)
{
    using (var secondEnumerator = second.GetEnumerator())
    {
        foreach (var item in first)
        {
            yield return item;

            if (secondEnumerator.MoveNext())
                yield return secondEnumerator.Current;
        }

        while (secondEnumerator.MoveNext())
            yield return secondEnumerator.Current;
    }
}
...