Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create Items from 3 collections using Linq

Tags:

c#

linq

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 ?

like image 573
Yoann. B Avatar asked Mar 12 '11 17:03

Yoann. B


5 Answers

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

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

Cons

  • same type required for all sequences (not a problem in many situations)
  • no checking for same list length (add a line if you need it)

Usage

Zipping colors

like image 56
Robert Synoradzki Avatar answered Sep 18 '22 19:09

Robert Synoradzki


Since 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.

like image 20
Ani Avatar answered Sep 20 '22 19:09

Ani


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();
like image 27
BrokenGlass Avatar answered Sep 18 '22 19:09

BrokenGlass


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 );
      }
    }
  }
}
like image 22
Tony Tanzillo Avatar answered Sep 17 '22 19:09

Tony Tanzillo


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();
like image 31
Bala R Avatar answered Sep 18 '22 19:09

Bala R