I have two lists, I need to make another list with the same elements than the other lists. But elements on this list need the next sequence.
list1[0], list2[0], list1[1], list2[1], ... and so on
for that, I made this code.
List<string> list1 = new List<string> { "rabbit", "dog", "cat", "shark" };
List<string> list2 = new List<string> { "red", "blue", "green", "black" };
var mixStuff = list1.Zip(list2, (c, a) => new { colors = c, animals = a });
List<string> newThings = new List<string>();
foreach(var mix in mixStuff)
{
newThings.Add(mix.colors);
newThings.Add(mix.animals);
}
I wonder if is there any way to do this better/cleaner. Thanks!
2 Answers 2
This is a great opportunity to write your own extension method to merge IEnumerable
s
public static class EnumerableMergeExtension
{
public static IEnumerable<TSource> Merge<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
if (first is null) throw new ArgumentNullException(nameof(first));
if (second is null) throw new ArgumentNullException(nameof(second));
return MergeIterator(first, second);
}
private static IEnumerable<TSource> MergeIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second)
{
using var firstEnumerator = first.GetEnumerator();
using var secondEnumerator = second.GetEnumerator();
var firstHasNext = firstEnumerator.MoveNext();
var secondHasNext = secondEnumerator.MoveNext();
do
{
if (firstHasNext)
{
yield return firstEnumerator.Current;
firstHasNext = firstEnumerator.MoveNext();
}
if (secondHasNext)
{
yield return secondEnumerator.Current;
secondHasNext = secondEnumerator.MoveNext();
}
}
while (firstHasNext || secondHasNext);
}
}
By manually advancing the enumerators you can handle enumerables of different lengths, even empty enumerables. The function is split in two to ensure eager parameter validation and lazy enumeration.
It's also possible to extend Merge
to handle any number of enumerables without having to chain Zip
public static class EnumerableMergeExtension
{
public static IEnumerable<TSource> Merge<TSource>(this IEnumerable<TSource> first, params IEnumerable<TSource>[] rest)
{
if (first is null) throw new ArgumentNullException(nameof(first));
if (rest is null) throw new ArgumentNullException(nameof(rest));
var enumerables = new IEnumerable<TSource>[rest.Length + 1];
enumerables[0] = first;
for(int i = 0; i < rest.Length; i++)
{
enumerables[i+1] = rest[i] ?? throw new ArgumentNullException(string.Format("{0}[{1}]", nameof(rest), i));
}
return MergeIterator(enumerables);
}
private static IEnumerable<TSource> MergeIterator<TSource>(IEnumerable<TSource>[] sources)
{
var enumerators = new IEnumerator<TSource>[sources.Length];
try
{
for(int i = 0; i < sources.Length; i++)
{
enumerators[i] = sources[i].GetEnumerator();
}
var hasNexts = new bool[enumerators.Length];
bool MoveNexts()
{
var anyHasNext = false;
for (int i = 0; i < enumerators.Length; i++)
{
anyHasNext |= hasNexts[i] = enumerators[i].MoveNext();
}
return anyHasNext;
}
while(MoveNexts())
{
for (int i = 0; i < enumerators.Length; i++)
{
if(hasNexts[i])
{
yield return enumerators[i].Current;
}
}
}
}
finally
{
foreach (var enumerator in enumerators)
{
enumerator?.Dispose();
}
}
}
}
And now you can merge an additional list
List<string> list1 = new List<string> { "rabbit", "dog", "cat", "shark" };
List<string> list2 = new List<string> { "red", "blue", "green", "black" };
List<string> list3 = new List<string> { "tiny", "medium", "small", "huge", "none" };
//rabbit, red, dog, blue, cat, green, shark, black
Console.WriteLine(string.Join(", ", list1.Merge(list2)));
//rabbit, red, tiny, dog, blue, medium, cat, green, small, shark, black, huge, none
Console.WriteLine(string.Join(", ", list1.Merge(list2, list3)));
Next you could add additional overloads with selectors and so on.
The simplest solution I can think of
var interleavingMergedLists = list1
.Zip(list2, (lhs, rhs) => new[] {lhs, rhs})
.SelectMany(l => l)
.ToList();
- Rather than creating a new anonymous object / ValueTuple instead create an array.
- Then you can flatten that array with the
SelectMany
- And finally you can materialize your query with
ToList
Please note that this works fine only if both lists have the same size.
-
1\$\begingroup\$ Sorry for the delayed response, I wasn't at home. Thanks a lot for your reply! \$\endgroup\$Omar– Omar2021年06月24日 16:23:38 +00:00Commented Jun 24, 2021 at 16:23