Combining arrays of different lengths without losing value using Zip ()
In the following code, I concatenate two arrays of types int
and string
. The first length is greater than the second, and as a result, the last index (which is 5
) is not concatenated:
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 to combine it. For example, creating null
or an empty string after the last one in existence words
and using it to concatenate with the last index numbers
. I can not understand.
Edit: Result I get
1one 2two 3three 4four
Result I want
1one 2two 3three 4four 5
Thank!
Edit: not a duplicate, my other question is about calling a method on a null object.
source to share
You can easily write your own LINQ-like extension method that does this:
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 just use a loop for
:
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);
}
}
source to share
You can simply expand the smaller of the two collections before you pin them, for example. something like that:
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 with a utility method and be generic so that it works for any collections that have been archived.
It looks like someone wrote a generic implementation already in this StackExchange Programmers answer .
source to share