Подтвердить что ты не робот

Создание элементов из 3 коллекций с использованием Linq

У меня есть 3 коллекции с точно такими же подсчетами элементов.

Мне нужно создать новую коллекцию на основе этих трех значений элементов коллекции.

Пример:

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

Я пытаюсь добиться этого с помощью 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 списка огромны.

Любая помощь?

4b9b3361

Ответ 1

Поскольку вы говорите о List<T> (у которого есть быстрый индекс), и вы гарантируете, что все три списка имеют одинаковую длину, самый простой способ:

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, чтобы создать аналогичное поведение (как упоминалось в других ответах), хотя это выглядит довольно уродливо.

Ответ 2

Вы можете zip их вместе - это застегивает первый список2 и list3, а затем объединяет объединенный список вместе со списком1:

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

Ответ 3

Концепция сопоставление последовательностей или списков в аргументах функций возникла на языке программирования LISP, чуть более 50 лет назад. В LISP это тривиально из-за его нетипизированного и ориентированного на список характера. Но делать это на строго типизированном языке сложно, по крайней мере в плане решения общей проблемы отображения n последовательностей в функцию, которая принимает n аргументов.

Здесь слабый удар в том, что должно удовлетворить большинство потребностей:

// Methods that work like LISP (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 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 );
      }
    }
  }
}

Ответ 4

немного потрепанный, но это должно сработать.

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

Ответ 5

Вот упрощенная версия, которая принимает любое количество последовательностей (в виде массива) одного и того же типа и зашифровывает их вместе:

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, чтобы добавить еще одну последовательность каждый раз

Против

  • тот же тип, необходимый для всех последовательностей (не проблема во многих ситуациях)
  • не проверяется для той же длины списка (добавьте строку, если вам это нужно)

Использование

Цокольные цвета

Ответ 6

Вы можете попробовать что-то вроде следующего:

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

Если вы всегда будете выполнять это в списках, вы можете поместить .ElementAt(i) с помощью индексатора [i].

Ответ 7

Если вы уверены, что все списки имеют одинаковый размер, вы должны использовать for-loop:

        List<Item> combinedList = new List<Item>(list1.Count);

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

Это решение очень просто и легко понять, не требуется LINQ.