C# 使用 LINQ 将多个列表合并为一个列表
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14639481/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Merge multiple Lists into one List with LINQ
提问by tlum
Is there a slick way to merge multiple Lists into a single List using LINQ to effectively replicate this?
有没有一种巧妙的方法可以使用 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]));
}
Or, since the lists arrive separately, its more effective to merge them sequentially like the following.
或者,由于列表是分开到达的,按如下顺序合并它们会更有效。
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];
}
采纳答案by Jeff Mercado
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:
您实际上是在尝试压缩三个集合。如果只有 LINQZip()
方法支持同时压缩两个以上。但可惜,它一次只支持两个。但我们可以让它发挥作用:
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();
回答by dasblinkenlight
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();
回答by Dmitry Khryukin
var colours = red.Select((t, i) => new RGB(t, green[i], blue[i])).ToList();
回答by William
You can use Aggregate with Zip to zip an arbitrary number of IEnumerables in one go.
您可以使用 Aggregate with Zip 一次性压缩任意数量的 IEnumerables。
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.
通常,依靠 Zip 作为底层组合器可以避免输入长度变化的问题。
回答by Johnny
For what it's worth, I like LINQ and use it frequently, but sometimes the old-fashioned way is the best. Note these examples:
就其价值而言,我喜欢 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);
The output is:
输出是:
r1 == r2: True
r1 == r3: True
time1 00:00:00.0100071
time2 00:00:00.0170138
time3 00:00:00.0040028
r1 == r2:真
r1 == r3:真
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.
当然,在这种情况下(对于人类的感知)时间几乎不明显,所以它归结为偏好,但知道 #3 是迄今为止最快的,我倾向于在类型更复杂的关键性能领域使用它或者枚举可能很大。
Also, note the difference when using 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);
The output:
输出:
r1 == r2: True
r1 == r3: True
time1 00:00:00.0280393
time2 00:00:00.0089870
time3 00:00:00.0050041
r1 == r2:真
r1 == r3:真
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.
正如预期的那样,.zip 方法必须进行多次迭代并且成为最慢的。
回答by Martin Liversage
Jeff Mercado provides an answer where three sequences are zipped. This can be generalized to any number of sequences with the limitation that all sequences then have to have the same item type.
Jeff Mercado 提供了一个答案,其中三个序列被压缩。这可以推广到任意数量的序列,但所有序列必须具有相同的项目类型。
Here is a generalized zip operator that handles varying input lengths and with suitable error handling and proper disposal of the enumerators:
这是一个通用的 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();
}
}
}
The colors can then be computed using this generalized zip operator:
然后可以使用这个通用的 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()));
The code may not be as elegant as some of the other solutions but a generalized zip operator might be useful in some situations and the code I have provided is efficient because it only iterates each source sequence once.
代码可能不像其他一些解决方案那么优雅,但通用 zip 运算符在某些情况下可能很有用,而且我提供的代码是高效的,因为它只迭代每个源序列一次。
回答by Robert Kope?
Here is a simplified version which takes anynumber 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
- 任意数量的序列
- 四行代码
- LINQ
.Zip()
方法的另一个重载 - 一次压缩所有序列而不是链接
.Zip
以每次添加一个序列
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
用法
回答by Mustafa Rezaei
use SelectMany like this:
像这样使用 SelectMany:
List_A.Select(a => a.List_B).SelectMany(s => s).ToList();