Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge different length arrays without losing a value using Zip()

Tags:

arrays

c#

.net

linq

In the following code, I'm merging two arrays of types int and string. The first one's length is bigger than the second one, and as a result, the last index (which is 5) doesn't get merged:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
string[] words = new string[] { "one", "two", "three", "four" };

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach (var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

I would like to know a way of getting it merged. For example, creating a null or empty string after the last one that exists in words and using it to merge with the last numbers index. Couldn't figure it out.

Edit: Result I get

1one
2two
3three
4four

Result I want

1one
2two
3three
4four
5

Thanks!

Edit: Not a duplicate, my other question is about calling method on a null object.

like image 395
first timer Avatar asked May 05 '15 03:05

first timer


2 Answers

You can easily write your own LINQ-like extension method that will do it:

public static class MyEnumerable
{
    public static IEnumerable<TResult> ZipWithDefault<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> selector)
    {
        bool firstMoveNext, secondMoveNext;

        using (var enum1 = first.GetEnumerator())
        using (var enum2 = second.GetEnumerator())
        {
            while ((firstMoveNext = enum1.MoveNext()) & (secondMoveNext = enum2.MoveNext()))
                yield return selector(enum1.Current, enum2.Current);

            if (firstMoveNext && !secondMoveNext)
            {
                yield return selector(enum1.Current, default(TSecond));
                while (enum1.MoveNext())
                {
                    yield return selector(enum1.Current, default(TSecond));
                }
            }
            else if (!firstMoveNext && secondMoveNext)
            {
                yield return selector(default(TFirst), enum2.Current);
                while (enum2.MoveNext())
                {
                    yield return selector(default(TFirst), enum2.Current);
                }
            }
        }
    }
}

But if your source is always a pair of arrays, it might be easier to simply use for loop:

public static IEnumerable<TResult> ZipWithDefault<TFirst, TSecond, TResult>(this TFirst[] first, TSecond[] second, Func<TFirst, TSecond, TResult> selector)
{
    var maxLength = Math.Max(first.Length, second.Length);

    for(var i = 0; i < maxLength; i++)
    {
        var firstItem = i < first.Length ? first[i] : default(TFirst);
        var secondItem = i < second.Length ? second[i] : default(TSecond);
        yield return selector(firstItem, secondItem);

    }
}
like image 147
MarcinJuraszek Avatar answered Sep 29 '22 14:09

MarcinJuraszek


You can just extend the smaller of the two collections before zipping them up, e.g. something like this:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
string[] words = new string[] { "one", "two", "three", "four" };

IEnumerable<string> wordsExtended = words;

if(words.Length < numbers.Length)
{
    wordsExtended = words.Concat(Enumerable.Repeat("", numbers.Length - words.Length));
}

var numbersAndWords = numbers.Zip(wordsExtended, (n, w) => new { Number = n, Word = w });
foreach (var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

Ideally you want to wrap this up in a utility method and be generic so it works for any collections that are zipped.

Looks like someone wrote a generic implementation already on this Programmers StackExchange answer.

like image 34
BrokenGlass Avatar answered Sep 29 '22 14:09

BrokenGlass