What's your favorite LINQ to Objects operator which is not built-in?

Viewed 6274

With extension methods, we can write handy LINQ operators which solve generic problems.

I want to hear which methods or overloads you are missing in the System.Linq namespace and how you implemented them.

Clean and elegant implementations, maybe using existing methods, are preferred.

43 Answers

GetBreadthFirstEnumerable

This is my favorite - does Breadth First Search over any collection quite easily. At each iteration, the method yield the current node, its parent, the level it is in the graph, and its index in breadth-first-order. This is great help in some scenarios, but if you don't need it, the method can be greatly simplified, if you want - you can yield only the node itself.

public class IteratedNode<T>
{
    public T Node;
    public T ParentNode;
    public int Level;
    public int Index;
}

/// <summary>
/// Iterates over a tree/graph via In Order Breadth First search.
/// </summary>
/// <param name="root">The root item.</param>
/// <param name="childSelector">A func that receives a node in the tree and returns its children.</param>
public static IEnumerable<IteratedNode<T>> GetBreadthFirstEnumerable<T>(this T root, Func<T, IEnumerable<T>> childSelector)
{
    var rootNode = new IteratedNode<T> { Node = root, ParentNode = default(T), Level = 1, Index = 1};
    var nodesToProcess = new Queue<IteratedNode<T>>( new[] {rootNode});

    int itemsIterated = 0;
    while (nodesToProcess.Count > 0)
    {
        IteratedNode<T> currentItem = nodesToProcess.Dequeue();

        yield return currentItem; itemsIterated++;

        // Iterate over the children of this node, and add it to queue, to process later.
        foreach (T child in childSelector(currentItem.Node))
        {
            nodesToProcess.Enqueue( 
                new IteratedNode<T> {
                    Node = child,
                    ParentNode = currentItem.Node,
                    Level = currentItem.Level + 1,
                    Index = itemsIterated
                });                      
        }
    }
}

Except with params list

This is a really simple wrapper that can make certain queries much easier:

public static IEnumerable<T> Except<T>(this IEnumerable<T> elements, params T[] exceptions)
{
    return elements.Except(exceptions);
}

Usage:

//returns a list of "work week" DayOfWeek values.
Enum.GetValues(typeof(DayOfWeek)).Except(DayOfWeek.Saturday, DayOfWeek.Sunday);

This prevents having to create your own collection to pass into Except, or using Where with a large Boolean "doesn't equal A and doesn't equal B and..." construct, making code a bit cleaner.

Except with Predicate (Inverse Where)

I have also seen Except given a predicate, basically making it an inverse of Where, and the easiest implementation is exactly that:

public static IEnumerable<T> Except<T>(this IEnumerable<T> elements, Predicate<T> predicate)
{
   return elements.Where(x=>!predicate(x));
}

Usage:

//best use is as a "natural language" statement already containing Where()
ListOfRecords.Where(r=>r.SomeValue = 123).Except(r=>r.Id = 2536);

If C# has both do-while and do-until constructs, this has its place.

ObjectWithMin/ObjectWithMax

Similar to Timwi's MinElement, but there's a more concise algorithm using the Aggregate extension method to spin through the source Enumerable:

public static T ObjectWithMax<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
    where TResult : IComparable<TResult>
{
    if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
    if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");

    //Set up the "seed" (current known maximum) to the first element
    var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();

    //run through all other elements of the source, comparing the projection of each
    //to the current "seed" and replacing the seed as necessary. Last element wins ties.
    return elements.Skip(1).Aggregate(seed,
                              (s, x) =>
                              projection(x).CompareTo(s.Projection) >= 0
                                  ? new {Object = x, Projection = projection(x)}
                                  : s
        ).Object;
}

public static T ObjectWithMin<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
    where TResult : IComparable<TResult>
{
    if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
    if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");

    var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();

    //ties won by the FIRST element in the Enumerable
    return elements.Aggregate(seed,
                              (s, x) =>
                              projection(x).CompareTo(s.Projection) < 0
                                  ? new {Object = x, Projection = projection(x)}
                                  : s
        ).Object;
}

I have found these methods invaluable compared to OrderBy().First() or Where(x=>x.SomeField == source.Min(someField)).First(). It's linear, goes through the Enumerable only once, and is stable to the current order of the list.

Partition

Will split an IEnumerable into two ILists based on a condition

public static void Partition<T>(this IEnumerable<T> source, Func<T, bool> predicate, ref IList<T> matches, ref IList<T> nonMatches)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));
    if (predicate == null)
        throw new ArgumentNullException(nameof(source));

    var _matches = new List<T>();
    var _nonMatches = new List<T>();
    foreach (var itm in source)
    {
        if (predicate(itm))
            _matches.Add(itm);
        else
            _nonMatches.Add(itm);
    }

    if (matches == null)
        matches = new List<T>();
    else
        matches.Clear();
    if (nonMatches == null)
        nonMatches = new List<T>();
    else
        nonMatches.Clear();

    foreach (var m in _matches)
        matches.Add(m);
    nonMatches.Clear();
    foreach (var m in _nonMatches)
        nonMatches.Add(m);
    nonMatches = _nonMatches;
}
Related