Merge multiple Lists into one List with LINQ

前端 未结 8 1761
南方客
南方客 2020-11-28 12:50

Is there a slick way to merge multiple Lists into a single List using LINQ to effectively replicate this?

public class RGB
{
    public int Red { get; set; }         


        
相关标签:
8条回答
  • 2020-11-28 13:14

    Yes - you can do it like this:

    List<int> red = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };
    List<int> green = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };
    List<int> blue = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };
    
    List<RGB> colors = Enumerable
        .Range(0, red.Count)
        .Select(i => new RGB(red[i], green[i], blue[i]))
        .ToList();
    
    0 讨论(0)
  • 2020-11-28 13:14

    For what it's worth, I like LINQ and use it frequently, but sometimes the old-fashioned way is the best. Note these examples:

            const int Max = 100000;
            var rnd = new Random();
            var list1 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
            var list2 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
    
            DateTime start;
    
            start = DateTime.Now;
            var r1 = list1.Zip(list2, (a, b) => new { a, b }).ToList();
            var time1 = DateTime.Now - start;
    
            start = DateTime.Now;
            var r2 = list1.Select((l1, i) => new { a = l1, b = list2[i]}).ToList();
            var time2 = DateTime.Now - start;
    
            start = DateTime.Now;
            var r3 = new int[0].Select(i => new { a = 0, b = 0 }).ToList();
            //  Easy out-of-bounds prevention not offered in solution #2 (if list2 has fewer items)
            int max = Math.Max(list1.Count, list2.Count);
            for (int i = 0; i < max; i++)
                r3.Add(new { a = list1[i], b = list2[i] });
            var time3 = DateTime.Now - start;
    
            Debug.WriteLine("r1 == r2: {0}", r1.SequenceEqual(r2));
            Debug.WriteLine("r1 == r3: {0}", r1.SequenceEqual(r3));
            Debug.WriteLine("time1 {0}", time1);
            Debug.WriteLine("time2 {0}", time2);
            Debug.WriteLine("time3 {0}", time3);
    

    The output is:

    r1 == r2: True
    r1 == r3: True
    time1 00:00:00.0100071
    time2 00:00:00.0170138
    time3 00:00:00.0040028

    Of course, the time is barely noticeable in this case (to human perception) so it comes down to preference, but knowing #3 is by far the fastest, I'd tend to use it in critical performance areas where the types were more complex or the enumerables could be large.

    Also, note the difference when using 3:

            const int Max = 100000;
            var rnd = new Random();
            var list1 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
            var list2 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
            var list3 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
    
            DateTime start;
    
            start = DateTime.Now;
            var r1 = list1.Zip(list2, (a, b) => new { a, b }).Zip(list3, (ab, c) => new { ab.a, ab.b, c }).ToList();
            var time1 = DateTime.Now - start;
    
            start = DateTime.Now;
            var r2 = list1.Select((l1, i) => new { a = l1, b = list2[i], c = list3[i] }).ToList();
            var time2 = DateTime.Now - start;
    
            start = DateTime.Now;
            var r3 = new int[0].Select(i => new { a = 0, b = 0, c = 0 }).ToList();
            //  Easy out-of-bounds prevention not offered in solution #2 (if list2 or list3 have fewer items)
            int max = new int[] { list1.Count, list2.Count, list3.Count }.Max();
            for (int i = 0; i < max; i++)
                r3.Add(new { a = list1[i], b = list2[i], c = list3[i] });
            var time3 = DateTime.Now - start;
    
            Debug.WriteLine("r1 == r2: {0}", r1.SequenceEqual(r2));
            Debug.WriteLine("r1 == r3: {0}", r1.SequenceEqual(r3));
            Debug.WriteLine("time1 {0}", time1);
            Debug.WriteLine("time2 {0}", time2);
            Debug.WriteLine("time3 {0}", time3);
    

    The output:

    r1 == r2: True
    r1 == r3: True
    time1 00:00:00.0280393
    time2 00:00:00.0089870
    time3 00:00:00.0050041

    As expected, the .zip method has to do multiple iterations and becomes the slowest.

    0 讨论(0)
  • use SelectMany like this:

    List_A.Select(a => a.List_B).SelectMany(s => s).ToList();
    
    0 讨论(0)
  • 2020-11-28 13:29

    You're essentially trying to zip up three collections. If only the LINQ Zip() method supported zipping up more than two simultaneously. But alas, it only supports only two at a time. But we can make it work:

    var reds = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };
    var greens = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };
    var blues = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };
    
    var colors =
        reds.Zip(greens.Zip(blues, Tuple.Create),
            (red, tuple) => new RGB(red, tuple.Item1, tuple.Item2)
        )
        .ToList();
    

    Of course it's not terribly painful to write up an extension method to do three (or more).

    public static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        IEnumerable<TThird> third,
        Func<TFirst, TSecond, TThird, TResult> resultSelector)
    {
        using (var enum1 = first.GetEnumerator())
        using (var enum2 = second.GetEnumerator())
        using (var enum3 = third.GetEnumerator())
        {
            while (enum1.MoveNext() && enum2.MoveNext() && enum3.MoveNext())
            {
                yield return resultSelector(
                    enum1.Current,
                    enum2.Current,
                    enum3.Current);
            }
        }
    }
    

    This makes things a lot more nicer:

    var colors =
        reds.Zip(greens, blues,
            (red, green, blue) => new RGB(red, green, blue)
        )
        .ToList();
    
    0 讨论(0)
  • 2020-11-28 13:32

    Here is a simplified version which takes any number of sequences (as an array) of the same type and zips them together:

    public static IEnumerable<TResult> Zip<T, TResult>(this IEnumerable<T>[] sequences, Func<T[], TResult> resultSelector)
    {
        var enumerators = sequences.Select(s => s.GetEnumerator()).ToArray();
        while(enumerators.All(e => e.MoveNext()))
            yield return resultSelector(enumerators.Select(e => e.Current).ToArray());
    }
    

    Pros

    • any number of sequences
    • four lines of code
    • another overload for LINQ .Zip() method
    • zips all sequences at once instead of chaining .Zip to add one more sequence each time

    Cons

    • same type required for all sequences (not a problem in your situation)
    • no checking for same list length (add a line if you need it)

    Usage

    0 讨论(0)
  • 2020-11-28 13:34

    You can use Aggregate with Zip to zip an arbitrary number of IEnumerables in one go.

    Here's how you might do that with your example:

    var colorLists = new List<int>[] { red, green, blue };
    var rgbCount = red.Count;
    var emptyTriples =
        Enumerable.Repeat<Func<List<int>>>(() => new List<int>(), rgbCount)
        .Select(makeList => makeList());
    
    var rgbTriples = colorLists.Aggregate(
        emptyTriples,
        (partialTriples, channelValues) =>
            partialTriples.Zip(
                channelValues,
                (partialTriple, channelValue) =>
                {
                    partialTriple.Add(channelValue);
                    return partialTriple;
                }));
    
    var rgbObjects = rgbTriples.Select(
        triple => new RGB(triple[0], triple[1], triple[2]));
    

    Generally, relying on Zip as the underlying combiner avoids problems with varying input lengths.

    0 讨论(0)
提交回复
热议问题