在C#中使用组合lambda表达式合并两个列表

3

我需要按照以下函数所述的方式合并两个列表。这个实现使用递归并且有效,但是似乎不太优雅。有没有人知道用LINQ更好的方法?似乎应该有像SelectMany这样的东西可以引用外部(未扁平化)元素,但我找不到任何东西。

/// <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;
}
3个回答

5

Enumerable.Zip正是你所需要的。

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
              });

编辑:看来我没有考虑到“外部”压缩的样式,即使一个列表完成后也会继续。以下是解决方案:

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();
        }
    }
}

该函数可以轻松地修改以将布尔值传递给结果选择器函数,以表示第一个或第二个元素是否存在,如果您需要明确检查它们(而不是在lambda中使用default(TFirst)default(TSecond))。


不错,之前从未注意到这一点。我一直认为它与压缩有关 :p 它也会支持像Dmitry寻找的“外部合并”吗? - Kevin Nacios
重点在于结果选择器函数返回的内容;就像我的例子一样,你可以返回一个匿名类型,其中包含对原始项的引用以及组合结果(如果你想保留该信息)。 - Adam Maras
看起来 zip 在遍历其中一个可枚举对象结束后就退出了,因此如果第二个列表更长,则会省略那些结果。 - Kevin Nacios
好的,让我想出一个提供“外部”zip风格的解决方案。 - Adam Maras
谢谢,我尝试了yield方法,但无法得到适当的工作效果,我真的在寻找一种惰性求值版本,而这就是它。 - Dmitry
1
我喜欢方法名OuterZip - tm1

1
如何像这样的东西?
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));
}

0

仅使用一个好的老式循环有什么问题。当然,它不够花哨,但它非常直接,而且您不必使用递归。

var firstList = first.ToList();
var secondList = second.ToList();
var firstCount = first.Count();
var secondCount = second.Count();
var result = new List<T>();
for (int i = 0; i < firstCount || i < secondCount; i++)
{
    if (i >= firstCount)
        result.Add(secondList[i]);
    if (i >= secondCount)
        result.Add(firstList[i]);

    result.Add(combiningFunction(firstList[i], secondList[i]));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接