Подтвердить что ты не робот

Объединение нескольких списков в один список с помощью LINQ

Есть ли пятно способ объединить несколько списков в один список, используя LINQ, чтобы эффективно реплицировать это?

public class RGB
{
    public int Red { get; set; }
    public int Green { get; set; }
    public int Blue { get; set; }
    public RGB(int red, int green, int blue) { Red = red; Green = green; Blue = blue; }
}

public void myFunction()
{
    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 = new List<RGB>();

    colors.Add(new RGB(red[0], green[0], blue[0]));
    colors.Add(new RGB(red[1], green[1], blue[1]));
    colors.Add(new RGB(red[2], green[2], blue[2]));
    colors.Add(new RGB(red[3], green[3], blue[3]));
    colors.Add(new RGB(red[4], green[4], blue[4]));
}

Или, поскольку списки поступают раздельно, это более эффективно, чтобы объединить их последовательно, как показано ниже.

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

    public RGB(int red, int green, int blue) { Red = red; Green = green; Blue = blue; }
}

public void myFunction()
{
    List<int> red = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };

    List<RGB> colors = new List<RGB>();

    colors.Add(new RGB(red[0], 0, 0));
    colors.Add(new RGB(red[1], 0, 0));
    colors.Add(new RGB(red[2], 0, 0));
    colors.Add(new RGB(red[3], 0, 0));
    colors.Add(new RGB(red[4], 0, 0));

    List<int> green = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };

    colors[0].Green = green[0];
    colors[1].Green = green[1];
    colors[2].Green = green[2];
    colors[3].Green = green[3];
    colors[4].Green = green[4];

    List<int> blue = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };

    colors[0].Blue = blue[0];
    colors[1].Blue = blue[1];
    colors[2].Blue = blue[2];
    colors[3].Blue = blue[3];
    colors[4].Blue = blue[4];
}
4b9b3361

Ответ 1

В основном вы пытаетесь закрепить три коллекции. Если только метод LINQ Zip() поддерживает одновременную запись более двух. Но, увы, он поддерживает только два за раз. Но мы можем заставить его работать:

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();

Конечно, не очень больно писать метод расширения, чтобы сделать три (или более).

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);
        }
    }
}

Это делает вещи намного приятнее:

var colors =
    reds.Zip(greens, blues,
        (red, green, blue) => new RGB(red, green, blue)
    )
    .ToList();

Ответ 2

Да - вы можете сделать это следующим образом:

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();

Ответ 3

var colours = red.Select((t, i) => new RGB(t, green[i], blue[i])).ToList();

Ответ 4

Вот упрощенная версия, которая принимает любое количество последовательностей (в виде массива) одного и того же типа и зашифровывает их вместе:

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

  • любое количество последовательностей
  • четыре строки кода
  • другая перегрузка для метода LINQ .Zip()
  • застегивает все последовательности сразу, а не цепочки .Zip, чтобы добавить еще одну последовательность каждый раз

Против

  • тот же тип, необходимый для всех последовательностей (не проблема в вашей ситуации)
  • не проверяется для той же длины списка (добавьте строку, если вам это нужно)

Использование

Цокольные цвета

Ответ 5

Вы можете использовать Aggregate с Zip для zip произвольного количества IEnumerables за один раз.

Здесь вы можете сделать это с помощью своего примера:

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]));

Как правило, полагаясь на Zip в качестве основного объединителя, вы избегаете проблем с изменяющейся длиной ввода.

Ответ 6

Для чего это стоит, мне нравится LINQ и часто его использую, но иногда старомодный способ - лучший. Обратите внимание на следующие примеры:

        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);

Вывод:

r1 == r2: True
r1 == r3: True
время1 00: 00: 00.0100071
время2 00: 00: 00.0170138
time3 00: 00: 00.0040028

Конечно, время едва заметно в этом случае (для человеческого восприятия), поэтому оно сводится к предпочтению, но знание № 3 на сегодняшний день является самым быстрым, я бы использовал его в критических областях производительности, где типы были более сложными или перечислимые могли быть большими.

Также обратите внимание на разницу при использовании 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);

Выход:

r1 == r2: True
r1 == r3: True
time1 00: 00: 00.0280393
время2 00: 00: 00.0089870
time3 00: 00: 00.0050041

Как и ожидалось, метод .zip должен выполнять несколько итераций и становится самым медленным.

Ответ 7

Джефф Меркадо дает ответ, где три последовательности застряли. Это можно обобщить на любое количество последовательностей с ограничением, что все последовательности должны иметь один и тот же тип элемента.

Вот обобщенный zip-оператор, который обрабатывает различные входные длины и с подходящей обработкой ошибок и правильной утилизации счетчиков:

static class EnumerableExtensions {

  public static IEnumerable Zip<TSource, TResult>(
    this IEnumerable<IEnumerable<TSource>> source,
    Func<IEnumerable<TSource>, TResult> resultSelector
  ) {
    if (source == null)
      throw new ArgumentNullException("source");
    if (resultSelector == null)
      throw new ArgumentNullException("resultSelector");

    var enumerators = new List<IEnumerator<TSource>>();
    try {
      foreach (var enumerable in source) {
        if (enumerable == null)
          throw new ArgumentNullException();
        enumerators.Add(enumerable.GetEnumerator());
      }

      while (enumerators.Aggregate(true, (moveNext, enumerator) => moveNext && enumerator.MoveNext()))
        yield return resultSelector(enumerators.Select(enumerator => enumerator.Current));
    }
    finally {
      foreach (var enumerator in enumerators)
        enumerator.Dispose();
    }
  }

}

Затем цвета могут быть вычислены с использованием этого обобщенного оператора zip:

var reds = new[] { 0x00, 0x03, 0x06, 0x08, 0x09 };
var greens = new[] { 0x00, 0x05, 0x06, 0x07, 0x0a };
var blues = new[] { 0x00, 0x02, 0x03, 0x05, 0x09 };
var colors = new[] { reds, greens, blues }
  .Zip(rgb => new RGB(rgb.First(), rgb.Skip(1).First(), rgb.Skip(2).First()));

Код может быть не таким изящным, как некоторые из других решений, но в некоторых ситуациях может оказаться полезным обобщенный zip-оператор, и код, который я предоставил, эффективен, поскольку он выполняет только итерацию каждой исходной последовательности один раз.

Ответ 8

используйте SelectMany так:

List_A.Select(a => a.List_B).SelectMany(s => s).ToList();