Skip to content

Aggregate all or some/partial/none #899

Open
@atifaziz

Description

@atifaziz

I'd like to propose adding an overload of Aggregate that will use a function to determine the validity of each item in the source sequence. As long as items are valid, they will be accumulated and a function will be called at the end to turn the accumulator into a result. As soon as one item is invalid, the iteration of the source sequence will be halted and a function will be called to turn the partial accumulation into a result.

This enables one to implement the following strategies:

  • Accumulate all good items or none.
  • Accumulate all good items or the good ones so far (partial/some case).

A prototype of such an extension would be as follows:

static partial class MoreEnumerable
{
    public static TResult
        Aggregate<TFirst, TState, TSecond, TResult>(
            this IEnumerable<TFirst> source,
            TState seed,
            Func<TFirst, (bool, TSecond)> secondChooser,
            Func<TState, TSecond, TState> folder,
            Func<TState, TResult> allSelector,
            Func<TState, TFirst, TResult> partialSelector)
    {
        var state = seed;

        foreach (var first in source)
        {
            if (secondChooser(first) is (true, var second))
                state = folder(state, second);
            else
                return partialSelector(state, first);
        }

        return allSelector(state);
    }
}

This is like Choose + Aggregate rolled into one, but which cannot be done otherwise by combining the two without considerable and additional effort. The following example shows the above in action, together with how it differs from Choose:

using System;
using System.Collections.Immutable;
using MoreLinq;

var inputs =
    from s in new[]
    {
        "O,l,2,3,4,S,6,7,B,9",
        "0,1,2,3,4,5,6,7,8,9",
    }
    select new
    {
        Source = s,
        Tokens = s.Split(','),
    };

foreach (var input in inputs)
{
    var xs = input.Tokens.Choose(s => (int.TryParse(s, out var n), n));
    Console.WriteLine($"The good stuff: {string.Join(", ", xs)}");

    if (input.Tokens
             .Aggregate(ImmutableArray<int>.Empty,
                        s => (int.TryParse(s, out var n), n),
                        (a, n) => a.Add(n),
                        a => a,
                        (_, _) => default) is { IsDefault: false } ys)
    {
        Console.WriteLine($"All okay: {string.Join(", ", ys)}");
    }
    else
    {
        Console.WriteLine($"Input is bad: {input.Source}");
    }
}

The output of the above example will be as follows:

The good stuff: 2, 3, 4, 6, 7, 9
Input is bad: O,l,2,3,4,S,6,7,B,9
The good stuff: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
All okay: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions