Vetri
Vetri

Reputation: 71

Join 2 lists have different length by in LINQ

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.

Upvotes: 2

Views: 1301

Answers (5)

Servy
Servy

Reputation: 203830

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.

Upvotes: 1

DLL_Whisperer
DLL_Whisperer

Reputation: 827

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

Upvotes: 0

Andrey Nasonov
Andrey Nasonov

Reputation: 2629

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

Upvotes: 2

steliosbl
steliosbl

Reputation: 8921

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.

Upvotes: 1

Bozhidar Stoyneff
Bozhidar Stoyneff

Reputation: 3634

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

Upvotes: 0

Related Questions