Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging 2 lists in C# with a combiner lamba

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;
}
like image 406
Dmitry Avatar asked Dec 05 '25 17:12

Dmitry


2 Answers

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).

like image 187
Adam Maras Avatar answered Dec 08 '25 07:12

Adam Maras


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));
}
like image 26
Adriaan Stander Avatar answered Dec 08 '25 08:12

Adriaan Stander



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!