Search code examples
c#linqf#ienumerable

LINQ equivalent to Seq.scan


Does C# have a LINQ equivalent to F#'s Seq.scan--like fold but storing the aggregated value at every step? (One like reduce that doesn't require a starting state would work at least as well.)

If one does not exist, I can work around its absence by writing my own or using LanguageExt's method, but I prefer to use core libraries when possible.


Solution

  • C# does not have a direct LINQ equivalent to F#'s Seq.scan method.

    You can mimic this functionality with the LINQ Aggregator method by passing a collection as the seed value and performing lookups on the previous accumulations:

    public class Data
    {
        public int Count { get; set; }
    }
    
    var data = new List<Data> 
    { 
        new Data { Count = 1 }, 
        new Data { Count = 2 }, 
        new Data { Count = 1 }, 
        new Data { Count = 5 }, 
        new Data { Count = 3 } 
    };
    
    var accumulatedValues = data.Aggregate(new List<int>(), (current, nextValue) =>
    {
        var lastValue = current.Count > 0 ? current[current.Count - 1] : 0;
        var updatedValue = lastValue + nextValue.Count;
        current.Add(updatedValue);
        return current;
    });
    

    In addition, it is relatively trivial to create your own extension method that mimics the Aggregate function, but returns a list of all accumulation steps instead of a single value.

    public static class Extensions
    {
        public static IEnumerable<TAccumulate> Accumulate<TSource, TAccumulate>(
            this IEnumerable<TSource> source, 
            TAccumulate seed, 
            Func<TAccumulate, TSource, TAccumulate> accumulator)
        {
            var accumulation = new List<TAccumulate>();
    
            var current = seed;
            foreach (var item in source)
            {
                current = accumulator(current, item);
                accumulation.Add(current);
            }
    
            return accumulation;
        }
    }
    

    This works well for both collections of primitive values as well as collections of objects:

    var firstAccumulation = 
        Enumerable.Range(1, 10).Accumulate(0, (acc, newValue) => acc + newValue);
    

    or

    public class Data
    {
        public int Count { get; set; }
    }
    
    var data = new List<Data> 
    { 
        new Data { Count = 1 }, 
        new Data { Count = 2 }, 
        new Data { Count = 1 }, 
        new Data { Count = 5 }, 
        new Data { Count = 3 } 
    };
    
    var secondAccumulation = data.Accumulate(0, (acc, newValue) => acc + newValue.Count);