I've 3 collections with exactly the same items count.
I need to create a new collection based on these 3 collections item values.
Exemple :
List<double> list1;
List<double> list2;
List<double> list3;
List<Item> list4;
public class Item
{
public double Value1{get;set;}
public double Value2{get;set;}
public double Value3{get;set;}
}
I try to achieve this using Linq.
I tried :
var query = from pt in list1
from at in list2
from ct in list3
select new Item
{
Value1 = pt,
Value2 = at,
Value3 = ct
};
But i got a OutOfMemoryException, my 3 lists are huge.
Any help ?
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());
}
.Zip()
method.Zip
to add one more sequence each timeSince you're talking about List<T>
(which has a fast indexer), and you provide the guarantee that all three lists are of the same length, the easiest way would be:
var items = from index in Enumerable.Range(0, list1.Count)
select new Item
{
Value1 = list1[index],
Value2 = list2[index],
Value3 = list3[index]
};
This approach obviously won't work well with collections that don't support fast indexers. A more general approach would be to write a Zip3
method, such as the one that comes with the F# Collections.Seq
module: Seq.zip3<'T1,'T2,'T3>
. Otherwise, you could chain two Enumerable.Zip
calls together to produce similar behaviour (as mentioned in other answers), although this does look quite ugly.
You could zip them together - this is zipping up first list2 and list3, then zips the combined list together with list1:
list4 = list1.Zip(list2.Zip(list3, (b, c) => new { b, c }),
(a, b) => new Item { Value1 = a, Value2 = b.b, Value3 = b.c })
.ToList();
The concept of mapping sequences or lists into the arguments of functions originated in the LISP programming language, a bit over 50 years ago. In LISP it is trivial because of its untyped and list-oriented nature. But, doing it in a strongly-typed language is difficult, at least in terms of solving the general problem of mapping n sequences to a function that takes n arguments.
Here's a feeble stab at what should accommodate most needs:
// 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<int, string, double, string> 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<T> Map<A1, A2, T>(
this Func<A1, A2, T> func,
IEnumerable<A1> a1,
IEnumerable<A2> a2 )
{
using( IEnumerator<A1> e1 = a1.GetEnumerator() )
using( IEnumerator<A2> 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<T> Map<A1, A2, A3, T>( this
this Func<A1, A2, A3, T> func,
IEnumerable<A1> a1,
IEnumerable<A2> a2,
IEnumerable<A3> a3 )
{
using( IEnumerator<A1> e1 = a1.GetEnumerator() )
using( IEnumerator<A2> e2 = a2.GetEnumerator() )
using( IEnumerator<A3> 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<T> Map<A1, A2, A3, A4, T>(
this Func<A1, A2, A3, A4, T> func,
IEnumerable<A1> a1,
IEnumerable<A2> a2,
IEnumerable<A3> a3,
IEnumerable<A4> a4 )
{
using( IEnumerator<A1> e1 = a1.GetEnumerator() )
using( IEnumerator<A2> e2 = a2.GetEnumerator() )
using( IEnumerator<A3> e3 = a3.GetEnumerator() )
using( IEnumerator<A4> 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 );
}
}
}
}
a little shabby but this should work.
List<Item> list4 =
list1.Select((l1i, i) => new Item {Value1 = l1i, Value2 = list2[i], Value3 = list3[i]}).ToList();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With