The general method to aggregate results is the Aggregate
extension method.
tl;rd for the simplest case:
slist.Select(row => row.Select(str => str.Length))
.Aggregate(l, r) => l.Zip(r, Math.Max);
First lets get the data in the form we want: A list of lengths
var llist = slist.Select(row => row.Select(str => str.Length));
Now we have a list of lists of length. Much easier to work with.
Aggregate works by keeping some running total, and running a function of which the parameters are the aggregate and the next row, yielding a new aggregate. You can optionally provide a seed value. It looks like this:
List<Int> seed = ???
Func<IEnumerable<Int>, IEnumerable<Int>, IEnumerable<Int>> accumulator = ???
llist.Aggregate(seed, accumulator);
From here on, I assume that the number of "columns" is variable.
The seed value is new List<int>()
. If there are 0 rows, that's your result.
List<Int> seed = new List<Int>();
Func<List<Int>, List<Int>, List<Int>> accumulator = ???
llist.Aggregate(seed, accumulator);
For each row, the new aggregate is the max of each element of the aggregate and the next row.
This happens to be exactly what the .Zip method is for if the items are guaranteed to be of the same length: https://msdn.microsoft.com/en-us/library/vstudio/dd267698(v=vs.100).aspx
giving
private IEnumerable<Int> accumulate(aggregate, row){
aggregate.Zip(row, (i1, i2) => Math.max(i1, i2));
}
unfortunately we don't have that guarantee, so we'll have to provide our own ZipAll. The rules are: If it an item exits in both, take the max. If it only exists in one, take that one. This is a bit of a potatoe; ZipAll should really be a library function.
IEnumerable<T> ZipAll(IEnumerable<T> left, IENumerable<T> right, Func<T, T, T> selector) {
var le = left.GetEnumerator;
var re = right.GetEnumerator;
while(le.MoveNext()){
if (re.MoveNext()) {
yield return selector(le.Current, re.Current);
} else {
yield return le.Current;
}
}
while(re.MoveNext()) yield return re.Current;
}
(1)
That gives us
List<Int> seed = new List<Int>();
Func<List<Int>, List<Int>, List<Int>> accumulator = (l, r) => ZipAll(l, r, Math.Max);
llist.Aggregate(seed, accumulator);
If you inline everything(accept our custom ZipAll function) that becomes
llist.Aggregate(new List<Int>(), (l, r) => ZipAll(l, r, Math.Max));
If you have at least one row, you can leave out the seed (the first row becomes the seed) to get
llist.Aggregate((l, r) => ZipAll(l, r, Math.Max));
In case the number of columns is constant, we can use the build in Zip
llist.Aggregate((l, r) => l.Zip(r, Math.Max));
(1)Outside the scope of this question, a more general overload is
IEnumerable<TResult, TLeft, TRight> ZipAll(IEnumerable<TLeft> left, IENumerable<TRight> right, Func<TResult, TLeft, TRight> selector, Func<TResult, TLeft> leftSelector, Func<Tresult, TRight> rightselector) {
var le = left.GetEnumerator;
var re = right.GetEnumerator;
while(le.MoveNext()){
if (re.MoveNext()) {
yield return selector(le.Current, re.Current);
} else {
yield return leftSelector(le.Current);
}
}
while(re.MoveNext()) yield return rightSelector(re.Current);
}
With this we could write our earlier ZipAll in terms of this one with the left and right projections as identity function as
IEnumerable<T> ZipAll(this IEnumerable<T> left, IENumerable<T> right, Func<T, T, T> selector) {
return ZipAll(left, right, selector, id => id, id => id);
}