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

发布于 2024-10-21 07:38:38 字数 772 浏览 1 评论 0原文

我有 3 个收藏品数量完全相同。

我需要根据这 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 个列表很大。

有什么帮助吗?

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 ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

待天淡蓝洁白时 2024-10-28 07:38:38

由于您正在谈论 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 一起调用以产生类似的行为(如其他答案中提到的),尽管这看起来确实很丑陋。

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.

画尸师 2024-10-28 07:38:38

您可以将它们压缩在一起 - 这是首先压缩 list2 和 list3,然后将组合列表与 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();

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();
大海や 2024-10-28 07:38:38

将序列或列表映射到函数的参数的概念起源于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<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 );
      }
    }
  }
}

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 );
      }
    }
  }
}
挽梦忆笙歌 2024-10-28 07:38:38

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

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

优点

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

缺点

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

用法

压缩颜色

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

肤浅与狂妄 2024-10-28 07:38:38

有点破旧,但这应该可以。

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

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();
烟火散人牵绊 2024-10-28 07:38:38

您可以尝试如下操作:

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

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

You can try something like the following:

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

If you're always going to be performing this on lists, you can place .ElementAt(i) with the [i] indexer.

爱的十字路口 2024-10-28 07:38:38

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

        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。

If you are sure all the lists are of equal size you should use a 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]
            });
        }

This solution is very simple and easy to understand, no LINQ required.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文