1
\$\begingroup\$

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!

\$\endgroup\$
0

2 Answers 2

4
\$\begingroup\$

This is a great opportunity to write your own extension method to merge IEnumerables


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.

\$\endgroup\$
3
\$\begingroup\$

The simplest solution I can think of

var interleavingMergedLists = list1
  .Zip(list2, (lhs, rhs) => new[] {lhs, rhs})
  .SelectMany(l => l)
  .ToList(); 
  1. Rather than creating a new anonymous object / ValueTuple instead create an array.
  2. Then you can flatten that array with the SelectMany
  3. And finally you can materialize your query with ToList

Please note that this works fine only if both lists have the same size.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Sorry for the delayed response, I wasn't at home. Thanks a lot for your reply! \$\endgroup\$
    – Omar
    Jun 24, 2021 at 16:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.