Объединение нескольких списков в один список с помощью 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];
}
Ответы
Ответ 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();