Создание элементов из 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 списка огромны.
Любая помощь?
Ответы
Ответ 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.