Join two lists of different length

亡梦爱人 提交于 2020-12-05 05:01:06

问题


I am using LINQ:

List<String> listA = new List<string>{"a", "b", "c", "d", "e", "f", "g"};  
List<String> listB = new List<string>{"1", "2", "3"};  

Desired result:

{"a", "1", "b", "2", "c", "3", "d", "1", "e", "2", "f", "3", "g", "1"}  

I tried but fail:

var mix = ListA.Zip(ListB, (l1, l2) => new[] { l1, l2 }).SelectMany(x => x);  
//Result : {"a", "1", "b", "2", "c", "3"}  

var mix = ListA.Zip(ListB, (a, b) => new[] { a, b })
        .SelectMany(x => x)
        .Concat(ListA.Count() < ListB.Count() ? ListB.Skip(ListA.Count()) : ListA.Skip(ListB.Count()))
        .ToList();  
//Result : {"a", "1", "b", "2", "c", "3", "d", "e", "f", "g"}  

How can I do this using LINQ?


回答1:


This works, even if I am not sure why you need it as linq expression:

var mix = Enumerable
           .Range(0, Math.Max(listA.Count, listB.Count))
           .Select(i => new[] { listA[i % listA.Count], listB[i % listB.Count] })
           .SelectMany(x => x);



回答2:


And how about implement your own wersion of Zip?

using System;
using System.Collections.Generic;
using System.Linq;

namespace SO
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            List<String> listA = new List<string> {"a", "b", "c", "d", "e", "f", "g"};
            List<String> listB = new List<string> {"1", "2", "3"};

            var mix = listA.ZipNew(listB, (l1, l2) => new[] {l1, l2}).SelectMany(x => x);

            foreach (var m in mix)
            {
                Console.WriteLine(m);
            }
        }
    }

    public static class Impl
    {
        public static int A(this int a)
        {
            return 1;
        }

        public static IEnumerable<TResult> ZipNew<TFirst, TSecond, TResult>( 
            this IEnumerable<TFirst> first, 
            IEnumerable<TSecond> second, 
            Func<TFirst, TSecond, TResult> resultSelector) 
        { 
            using (IEnumerator<TFirst> iterator1 = first.GetEnumerator()) 
            using (IEnumerator<TSecond> iterator2 = second.GetEnumerator())
            {
                var i1 = true;
                var i2 = true;
                var i1Shorter = false;
                var i2Shorter = false;
                var firstRun = true;


                while(true) 
                {
                    i1 = iterator1.MoveNext();
                    i2 = iterator2.MoveNext();

                    if (!i1 && (i1Shorter || firstRun))
                    {
                        iterator1.Reset();
                        i1 = iterator1.MoveNext();
                        i1Shorter = true;
                        firstRun = false;
                    }

                    if (!i2 && (i2Shorter || firstRun))
                    {
                        iterator2.Reset();
                        i2 = iterator2.MoveNext();
                        i2Shorter = true;
                        firstRun = false;
                    }

                    if (!(i1 && i2))
                    {
                        break;
                    }

                    yield return resultSelector(iterator1.Current, iterator2.Current); 
                }
            } 
        }
    }
}



回答3:


There is only version that tries to do the right thing and uses the right approach so far and it's the one by BWA. This can be simplified a little bit to this extension so I'll leave it here for reference:

public static IEnumerable<TResult> ZipLoop<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector)
{
    if (first is null) throw new ArgumentNullException(nameof(first));
    if (second is null) throw new ArgumentNullException(nameof(second));
    if (resultSelector is null) throw new ArgumentNullException(nameof(resultSelector));

    var (firstCycle, secondCycle) = (false, false);

    var e1 = first.GetEnumerator();
    var e2 = second.GetEnumerator();
    try
    {
        while (true)
        {
            if (!TryMoveNextOrLoop(first, ref e1, ref firstCycle)) yield break;
            if (!TryMoveNextOrLoop(second, ref e2, ref secondCycle)) yield break;
            if (firstCycle && secondCycle) yield break;
            yield return resultSelector(e1.Current, e2.Current);
        }
    }
    finally
    {
        e1?.Dispose();
        e2?.Dispose();
    }
}

private static bool TryMoveNextOrLoop<T>(IEnumerable<T> source, ref IEnumerator<T> enumerator, ref bool cycle)
{
    if (!enumerator.MoveNext())
    {
        cycle = true;
        enumerator.Dispose();
        enumerator = source.GetEnumerator();
        return enumerator.MoveNext();           
    }
    return true;
}



回答4:


I would go with the for loop approach

List<String> listA = new List<string> { "a", "b", "c", "d", "e", "f", "g" };
List<String> listB = new List<string> { "1", "2", "3" };

List<string> listC = new List<string>();
for (int i = 0; i < Math.Max(listA.Count, listB.Count); i++)
{
    listC.Add(listA[i % listA.Count]);
    listC.Add(listB[i % listB.Count]);
}



回答5:


How about this:

listA.SelectMany((a, i) => new[] { a, listB[i % listB.Count]})



回答6:


You were very close to the (an) answer. How about this?

var listA = new List<string> { "a", "b", "c", "d", "e", "f", "g" };
var listB = new List<string> { "1", "2", "3" };

// "a", "b", "c", "d", "e", "f", "g", "a", "b", "c", "d", "e", "f", "g", etc.
// This is lazily evaluated so it doesn't matter that there are
// more elements than needed here.
var repeatedA = Enumerable.Repeat(listA, listB.Count)
    .SelectMany(list => list);

// "1", "2", "3", "1", "2", "3", etc. This is lazily evaluated
// so it doesn't matter that there are more elements than needed here.
var repeatedB = Enumerable.Repeat(listB, listA.Count)
    .SelectMany(list => list);

var result = repeatedA.Zip(repeatedB, (a, b) => new[] { a, b })
    .SelectMany(x => x)
    .Take(2 * Math.Max(listA.Count, listB.Count));

// result is {"a", "1", "b", "2", "c", "3", "d", "1", "e", "2", "f", "3", "g", "1"}  



回答7:


try this...

var mix =Enumerable.Range(0, new[] { listA.Count, listB.Count }.Max()).Select(i => new[] { listA[i % listA.Count], listB[i % listB.Count] }).SelectMany(x => x);


来源:https://stackoverflow.com/questions/53169146/join-two-lists-of-different-length

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!