in functional terms this is a combination of :
zip
take two sequences and create a sequence of tuples of the elements
and
map
Take a function f
and a sequence and return a new sequence which is f(x) for each x in the original sequence
The zip is trivial in c# 4.0
Taking the simplistic implementation from there we have
static class Enumerable
{
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> func)
{
var ie1 = first.GetEnumerator();
var ie2 = second.GetEnumerator();
while (ie1.MoveNext() && ie2.MoveNext())
yield return func(ie1.Current, ie2.Current);
}
}
We then need the map. We already have it, it's what we call Select in c#
IEnumerable<int> input = { 1,2,3,4 };
int a = 0;
var accumulate = input.Select(x =>
{
a += x;
return a;
});
But it is safer to bake this into it's own method (no currying in c#) and allow support for arbitrary types/accumulations.
static class Enumerable
{
public static IEnumerable<T> SelectAccumulate<T>(
this IEnumerable<T> seq,
Func<T,T,T> accumulator)
{
var e = seq.GetEnumerator();
T t = default(T);
while (e.MoveNext())
{
t = accumulator(t, e.Current);
yield return t;
}
}
}
Then we can put them together like so
var input = new int[] {1,2,3};
var mapsum = input.Zip(
input.SelectAccumulate((x,y) => x+y),
(a,b) => new {a,b});
This will iterate over the sequence twice, but is more general. You could choose to do the accumulator yourself within a standard select and a simple closure but it is no longer so useful as a 'building block' which is one of the driving forces behind functional programming.
Tuple support is a pain except within a method as the anonymous types don't traverse method boundaries without quite a bit of hassle. A few basic tuples should be included in c# 4.0. assuming a tuple class/struct called Pair<T,U>
you could do:
public static IEnumerable<Pair<T,T>> ZipMapAccumulate<T>(
this IEnumerable<T> input,
Func<T,T,T> accumulator)
{
return input.Zip(
input.SelectAccumulate((x,y) => accumulator (x,y)),
(a,b) => new Pair<T,T>(a,b));
}
//get an int specific one
public static Func<IEnumerable<int>, IEnumerable<Pair<int,int>>>
ZipMapSum()
{
return input => Enumerable.ZipMapAccumulate(
input,
(i,j) => i + j);
}
Where c# linq becomes much more cumbersome than languages like f# is the poor support for operators, currying and tuples unless you keep everything inside one function and 'reconstruct it' each and every time for each type.