Join 2 lists have different length by in LINQ

自作多情 提交于 2021-02-05 11:13:24

问题


How can I join 2 lists of different lengths. it should join with the sequence. Eg.

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

I need to get result like below.

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

I tried this.

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

But its throwing error since the lists are having different lengths. Please help me to get the solution.


回答1:


This should work:

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

Do note the ? in the array declarations. That is necessary in order to have null values in the resulting list. Omitting the ? causes the result to have 0 instead of null.

If you can't or don't want to declare the arrays as int?, then you'll have to do the cast in the Select like so:

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

This second bit of code will work correctly with regular int arrays or Lists.




回答2:


The ugly but working version is the following:

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

Its drawback it double evaluation of a collection (the first one is by calling .Count()).

So it is better just to write an extension

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

and use it as

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



回答3:


Simply loop through the lists and construct new, let's say Dictionary<int?, int?> out of each list element:

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:


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

this should work for any IEnumerable




回答5:


What you have is effectively a Zip, but where it zips to the end of the longer, rather than the shorter, of the two sequences. You can write such a Zip method, with something that looks a bit similar to the actual Zip implementation:

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

With that you can write:

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

You could make the code a bit shorter by requiring the inputs to be lists, but the code wouldn't be any faster as lists, just less typing, and it's not like it's that much extra work to support any sequence, so I'd say it's worth the added few lines of code.



来源:https://stackoverflow.com/questions/46607179/join-2-lists-have-different-length-by-in-linq

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!