【问题标题】:Create Items from 3 collections using Linq使用 Linq 从 3 个集合创建项目
【发布时间】:2011-03-12 17:54:43
【问题描述】:

我有 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 个列表非常庞大。

有什么帮助吗?

【问题讨论】:

  • 等等 - 你什么时候收到 OutOfMemoryException?您显示的查询实际上并没有执行任何操作。当您遍历它们时,将创建新项目。除非您将每个项目都保存在内存中,否则这不会导致 OutOfMemory。
  • @Judah,我在调用 .ToList() 后得到了 OutOfMemoryException
  • 是的,因为那时您正在创建内存中的所有项目。你能避免调用 .ToList() 吗?更好的是,list1、list2 和 list3 可以是 IEnumerable 类型,而不是列表吗?如果您使用延迟执行,则不会将所有项目保留在内存中,因此您不会遇到 OutOfMemoryException。
  • 内存不足的原因是因为您正在对三个列表进行笛卡尔积,而不是将它们视为并行数组。这意味着您将获得三重嵌套循环的结果,而不是对所有三个列表使用相同索引的一个循环的结果。 Python 有一个zip 函数,它接受任意数量的集合并返回一个元组集合。 LINQ 的 Zip 函数尝试(但失败)做同样的事情,因为它一次只允许两个序列并且需要一个转换函数。两个列表都可以,但 3+ 是一场噩梦。

标签: c# linq


【解决方案1】:

既然您说的是List&lt;T&gt;(它有一个快速索引器),并且您保证所有三个列表的长度相同,那么最简单的方法是:

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&lt;'T1,'T2,'T3&gt;。否则,您可以将两个 Enumerable.Zip 调用链接在一起以产生类似的行为(如其他答案中所述),尽管这看起来确实很丑。

【讨论】:

  • 这是一个有用的答案。
【解决方案2】:

您可以zip 将它们放在一起 - 这是先压缩 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();

【讨论】:

    【解决方案3】:

    mapping sequences or lists into the arguments of functions 的概念起源于 50 多年前的 LISP 编程语言。在 LISP 中,它是微不足道的,因为它的无类型和面向列表的性质。但是,用强类型语言做这件事是很困难的,至少在解决将 n 个序列映射到一个接受 n 个参数的函数的一般问题方面。 p>

    以下是应该满足大多数需求的微弱尝试:

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

    【讨论】:

      【解决方案4】:

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

      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 以每次添加一个序列

      缺点

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

      用法

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          您可以尝试以下方法:

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

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

          【讨论】:

            【解决方案7】:

            如果您确定所有列表的大小相同,则应使用 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。

            【讨论】:

            • 但与链接不同,这不是延迟加载的;一次性添加所有项目。鉴于他已经遇到内存不足异常,这将使问题变得更糟。
            猜你喜欢
            • 2014-03-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多