使用Linq从3个集合创建项目

我有3个完全相同的项目集合。

我需要根据这3个集合项值创建一个新集合。

例子:

List list1; List list2; List list3; List list4; public class Item { public double Value1{get;set;} public double Value2{get;set;} public double Value3{get;set;} } 

我尝试使用Linq实现这一目标。

我试过了 :

  var query = from pt in list1 from at in list2 from ct in list3 select new Item { Value1 = pt, Value2 = at, Value3 = ct }; 

但是我得到了OutOfMemoryException,我的3个列表很大。

有帮助吗?

由于您正在讨论List (具有快速索引器),并且您提供所有三个列表长度相同的保证,最简单的方法是:

 var items = from index in Enumerable.Range(0, list1.Count) select new Item { Value1 = list1[index], Value2 = list2[index], Value3 = list3[index] }; 

对于不支持快速索引器的集合,这种方法显然不适用。 更通用的方法是编写Zip3方法,例如F# Collections.Seq模块附带的方法: Seq.zip3<'T1,'T2,'T3> 。 否则,您可以将两个Enumerable.Zip调用链接在一起以产生类似的行为(如其他答案中所述),尽管这看起来非常难看。

你可以将它们压缩在一起 – 这是首先将list2和list3压缩,然后将组合列表与list1一起压缩:

 list4 = list1.Zip(list2.Zip(list3, (b, c) => new { b, c }), (a, b) => new Item { Value1 = a, Value2 = bb, Value3 = bc }) .ToList(); 

将序列或列表映射到函数参数中的概念起源于LISP编程语言,有点像50多年前。 在LISP中,由于其无类型和列表导向性质,它是微不足道的。 但是,以强类型语言进行操作是困难的,至少在解决将n个序列映射到带有n个参数的函数的一般问题方面是困难的。

对于应该满足大多数需求的内容,这是一个微弱的刺:

 // Methods that work like LISP's (mapcar) when used with // more than 1 list argument (2 to 4 included here, add // versions for more arguments as needed). // // The methods will only yield as many results as there // are elements in the argument sequence that yields the // fewest elements, in cases where argument sequences do // not all yield the same number of elements (which is // the same behavior as their LISP counterpart). // // An interesting twist, is that we make these methods // extension methods of the invoked function, because it // doesn't seem natural to make one of the sequences of // arguments the target. // // Nonetheless, they can still be called as non-extension // methods through the declaring type: // // (Untested): // // string[] fruit = new string[] // {"apples", "oranges", "pears", "banannas"}; // // double[] prices = new double[] {1.25, 1.50, 1.00, 0.75}; // // int[] amounts = new int[] {12, 8, 24, 5}; // // // Func func = // ( amount, name, price ) => string.Format( // "{{0} lbs. of {1} @ ${2:F2} / lb. = ${3:F2}", // amount, name, price, amount * price ); // // var invoice = func.Map( amounts, fruit, prices ); // // foreach( string item in invoice ) // Console.WriteLine( item ); // // It's also worth noting that CLR 3.5 introduces the // "Zip" extension method, that allows mapping of two // sequences to a function taking two arguments, but // without some wild contortion involving currying and // multiple calls to Zip, it can't solve the general // problem (mapping n sequences to a function taking // that many arguments). public static class Sequence { // Map elements of 2 sequences to the arguments of // a function taking 2 args, and return results: public static IEnumerable Map( this Func func, IEnumerable a1, IEnumerable a2 ) { using( IEnumerator e1 = a1.GetEnumerator() ) using( IEnumerator e2 = a2.GetEnumerator() ) { IEnumerator[] args = new IEnumerator[] {e1, e2}; while( args.TrueForAll( e => e.MoveNext() ) ) { yield return func( e1.Current, e2.Current ); } } } // 3 arguments public static IEnumerable Map( this this Func func, IEnumerable a1, IEnumerable a2, IEnumerable a3 ) { using( IEnumerator e1 = a1.GetEnumerator() ) using( IEnumerator e2 = a2.GetEnumerator() ) using( IEnumerator e3 = a3.GetEnumerator() ) { IEnumerator[] args = new IEnumerator[] {e1, e2, e3}; while( args.TrueForAll( e => e.MoveNext() ) ) { yield return func( e1.Current, e2.Current, e3.Current ); } } } // 4 arguments public static IEnumerable Map( this Func func, IEnumerable a1, IEnumerable a2, IEnumerable a3, IEnumerable a4 ) { using( IEnumerator e1 = a1.GetEnumerator() ) using( IEnumerator e2 = a2.GetEnumerator() ) using( IEnumerator e3 = a3.GetEnumerator() ) using( IEnumerator e4 = a4.GetEnumerator() ) { IEnumerator[] args = new IEnumerator[] {e1, e2, e3, e4}; while( args.TrueForAll( e => e.MoveNext() ) ) { yield return func( e1.Current, e2.Current, e3.Current, e4.Current ); } } } } 

有点破旧,但这应该工作。

  List list4 = list1.Select((l1i, i) => new Item {Value1 = l1i, Value2 = list2[i], Value3 = list3[i]}).ToList(); 

这是一个简化版本,它采用相同类型的任意数量的序列(作为数组)并将它们压缩在一起:

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

优点

  • 任意数量的序列
  • 四行代码
  • LINQ .Zip()方法的另一个重载
  • 拉链所有序列而不是链接.Zip每次添加一个序列

缺点

  • 所有序列都需要相同类型(在许多情况下不是问题)
  • 不检查相同的列表长度(如果需要,添加一行)

用法

压缩颜色

您可以尝试以下内容:

  var combined = list1.Select((x, i) => new Item {Value1 = x, Value2 = list2.ElementAt(i), list3 = range3.ElementAt(i)}); 

如果您总是要在列表上执行此操作,则可以将.ElementAt(i)[i]索引器放在一起。

如果您确定所有列表大小相同,则应使用for循环:

  List combinedList = new List(list1.Count); for (int i = 0; i < list1.Count; i++) { combinedList.Add(new Item() { Value1 = list1[i], Value2 = list2[i], Value3 = list3[i] }); } 

该解决方案非常简单易懂,无需LINQ。