在LINQ中加入2个列表具有不同的长度

时间:2017-10-06 13:43:36

标签: c# linq

如何加入2个不同长度的列表。它应该与序列一起加入。 例如

{1,2,3,4} with {5,6,7}

我需要得到如下的结果。

{{1,5}, {2,6}, {3,7}, {4,null}}

我试过了。

var qry = a.Select((i, index) => new {i, j = b[index]}); 

但是它的抛出错误因为列表有不同的长度。 请帮我解决问题。

5 个答案:

答案 0 :(得分:2)

丑陋但有效的版本如下:

a.Cast<int?>().Concat(Enumerable.Repeat<int?>(null, Math.Max(b.Count() - a.Count(), 0)))
    .Zip(b.Cast<int?>()
        .Concat(Enumerable.Repeat<int?>(null, Math.Max(a.Count() - b.Count(), 0))),
        (x, y) => new { x, y });

它的缺点是对集合进行双重评估(第一个是通过调用.Count())。

所以最好只写一个扩展名

static IEnumerable<TResult> ZipNull<T1, T2, TResult>(this IEnumerable<T1> a, IEnumerable<T2> b, Func<T1?, T2?, TResult> func)
    where T1 : struct
    where T2 : struct
{
    using (var it1 = a.GetEnumerator())
    using (var it2 = b.GetEnumerator())
    {
        while (true)
        {
            if (it1.MoveNext())
            {
                if (it2.MoveNext())
                {
                    yield return func(it1.Current, it2.Current);
                }
                else
                {
                    yield return func(it1.Current, null);
                }
            }
            else
            {
                if (it2.MoveNext())
                {
                    yield return func(null, it2.Current);
                }
                else
                {
                    break;
                }
            }
        }
    }
}

并将其用作

a.ZipNull(b, (x, y) => new { x, y });

答案 1 :(得分:1)

这应该有效:

var a = new int?[] { 1, 2, 3, 4 };
var b = new int?[] { 5, 6, 7 };

var result = Enumerable.Range(0, Math.Max(a.Count(), b.Count()))
                       .Select(n => new[] {a.ElementAtOrDefault(n), b.ElementAtOrDefault(n)});

请注意数组声明中的?。为了在结果列表中具有空值,这是必要的。省略?会导致结果为0而不是null

如果您不能或不想将数组声明为int?,那么您必须在Select中执行强制转换:

var result = Enumerable.Range(0, Math.Max(a.Count(), b.Count()))
                       .Select(n => new[] { a.Select(i => (int?)i).ElementAtOrDefault(n), b.Select(i => (int?)i).ElementAtOrDefault(n) });

这第二段代码可以正常使用常规int数组或列表。

答案 2 :(得分:0)

var x = new[] { 1, 2, 3, 4 }.ToList();
var y = new[] { 5, 6, 7 }.ToList();
var arrayLists = new[] {x, y}.OrderBy(t => t.Count).ToList();
var result = arrayLists
            .Last()
            .Select((item, i) => new[] { x[i], i < arrayLists.First().Count ? y[i] : (int?)null })
            .ToList();

这适用于任何IEnumerable

答案 3 :(得分:0)

只需遍历列表并构建新的,让我们从每个列表元素中说出Dictionary<int?, int?>

var theFirstList = new List<int?> { 1, 2, 3, 4 };

var theSecondList = new List<int?> { 5, 6, 7 };

var el = new Dictionary<int?, int?>();

var length = Math.Max(theFirstList.Count, theSecondList.Count);

for (int i = 0; i < length; i++)
{
    el.Add(theFirstList.ElementAtOrDefault(i), theSecondList.ElementAtOrDefault(i));
}

答案 4 :(得分:0)

你所拥有的实际上是一个Zip,但它会拉伸到两个序列的较长而不是较短的末尾。您可以编写这样的Zip方法,其内容看起来与实际的Zip实现类似:

public static IEnumerable<TResult> ZipAll<TSource, TSecond, TResult>(this IEnumerable<TSource> source,
    IEnumerable<TSecond> other,
    Func<TSource, TSecond, TResult> projection)
{
    using (var firstIterator = source.GetEnumerator())
    using (var secondIterator = other.GetEnumerator())
    {
        while (true)
        {
            bool hasFirst = firstIterator.MoveNext();
            bool hasSecond = secondIterator.MoveNext();
            TSource first = hasFirst ? firstIterator.Current : default(TSource);
            TSecond second = hasSecond ? secondIterator.Current : default(TSecond);
            if (hasFirst || hasSecond)
                yield return projection(first, second);
            else
                yield break;
        }
    }
}

你可以这样写:

a.ZipAll(b, (i, j) => new { i, j });

你可以通过要求输入为列表来缩短代码,但代码不会像列表那样快,只需要更少的输入,并且它不像 那么多努力支持任何序列,所以我说它值得添加几行代码。