I need to merge two lists in a particular manner as the function describes below. This implementation uses recursion and works but seems kludgy. Does anyone know a better way to do this with LINQ, seems like there should be something like a SelectMany that can refer back to outer(unflattened) elements but I can't find anything
/// <summary>
/// Function merges two list by combining members in order with combiningFunction
/// For example (1,1,1,1,1,1,1) with
/// (2,2,2,2) and a function that simply adds
/// will produce (3,3,3,3,1,1,1)
/// </summary>
public static IEnumerable<T> MergeList<T>(this IEnumerable<T> first,
IEnumerable<T> second,
Func<T, T, T> combiningFunction)
{
if (!first.Any())
return second;
if (!second.Any())
return first;
var result = new List<T> {combiningFunction(first.First(), second.First())};
result.AddRange(MergeList<T>(first.Skip(1), second.Skip(1), combiningFunction));
return result;
}
Enumerable.Zip is exactly what you want.
var resultList = Enumerable.Zip(first, second,
// or, used as an extension method: first.Zip(second,
(f, s) => new
{
FirstItem = f,
SecondItem = s,
Sum = f + s
});
EDIT: It seems I didn't account for the "outer" style of zipping that continues even if one list completes. Here's a solution that accounts for that:
public static IEnumerable<TResult> OuterZip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> firstEnumerator = first.GetEnumerator())
using (IEnumerator<TSecond> secondEnumerator = second.GetEnumerator())
{
bool firstHasCurrent = firstEnumerator.MoveNext();
bool secondHasCurrent = secondEnumerator.MoveNext();
while (firstHasCurrent || secondHasCurrent)
{
TFirst firstValue = firstHasCurrent
? firstEnumerator.Current
: default(TFirst);
TSecond secondValue = secondHasCurrent
? secondEnumerator.Current
: default(TSecond);
yield return resultSelector(firstValue, secondValue);
firstHasCurrent = firstEnumerator.MoveNext();
secondHasCurrent = secondEnumerator.MoveNext();
}
}
}
This function could easily be modified to pass boolean values to the result selector function to denote whether or not a first or a second element exist, if you need to check for that explicitly (instead of working with default(TFirst) or default(TSecond) in the lambda).
How about somethine like
public static IEnumerable<T> MyMergeList<T>(this IEnumerable<T> first,
IEnumerable<T> second,
Func<T, T, T> combiningFunction)
{
return Enumerable.Range(0, Math.Max(first.Count(), second.Count())).
Select(x => new
{
v1 = first.Count() > x ? first.ToList()[x] : default(T),
v2 = second.Count() > x ? second.ToList()[x] : default(T),
}).Select(x => combiningFunction(x.v1, x.v2));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With